Make entering recents a lot faster

- Precache the bitmap for the window animation in the preload phase
- Remove some post's so we have a faster path from UP -> startActivity
- Don't dim the headers in the first frame drawn, because layer
creation is slow. Instead, do it in the second frame, when the window
animation is already running.

All these changes combined make going to recents about 40-50ms faster.

Change-Id: I3e4060af1ac57b3f359fe7f86f9e3814c6490323
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index bbd3e60..7d2b5c87 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -32,6 +32,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -40,6 +41,7 @@
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
+
 import com.android.systemui.R;
 import com.android.systemui.RecentsComponent;
 import com.android.systemui.SystemUI;
@@ -184,12 +186,16 @@
 
     // Header (for transition)
     TaskViewHeader mHeaderBar;
+    final Object mHeaderBarLock = new Object();
     TaskStackView mDummyStackView;
 
     // Variables to keep track of if we need to start recents after binding
     boolean mTriggeredFromAltTab;
     long mLastToggleTime;
 
+    Bitmap mThumbnailTransitionBitmapCache;
+    Task mThumbnailTransitionBitmapCacheKey;
+
     public Recents() {
     }
 
@@ -360,13 +366,16 @@
     void preloadRecentsInternal() {
         // Preload only the raw task list into a new load plan (which will be consumed by the
         // RecentsActivity)
+        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
+        MutableBoolean topTaskHome = new MutableBoolean(true);
         RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
         sInstanceLoadPlan = loader.createLoadPlan(mContext);
-
-        ActivityManager.RunningTaskInfo topTask = mSystemServicesProxy.getTopMostTask();
-        MutableBoolean isTopTaskHome = new MutableBoolean(true);
-        if (topTask != null && mSystemServicesProxy.isRecentsTopMost(topTask, isTopTaskHome)) {
-            sInstanceLoadPlan.preloadRawTasks(isTopTaskHome.value);
+        if (topTask != null && !mSystemServicesProxy.isRecentsTopMost(topTask, topTaskHome)) {
+            sInstanceLoadPlan.preloadRawTasks(topTaskHome.value);
+            loader.preloadTasks(sInstanceLoadPlan, topTaskHome.value);
+            TaskStack top = sInstanceLoadPlan.getAllTaskStacks().get(0);
+            preCacheThumbnailTransitionBitmapAsync(topTask, top, mDummyStackView,
+                    topTaskHome.value);
         }
     }
 
@@ -513,12 +522,14 @@
         algo.computeRects(mWindowRect.width(), mWindowRect.height(), taskStackBounds);
         Rect taskViewSize = algo.getUntransformedTaskViewSize();
         int taskBarHeight = res.getDimensionPixelSize(R.dimen.recents_task_bar_height);
-        mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
-                false);
-        mHeaderBar.measure(
-                View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
-        mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
+        synchronized (mHeaderBarLock) {
+            mHeaderBar = (TaskViewHeader) mInflater.inflate(R.layout.recents_task_view_header, null,
+                    false);
+            mHeaderBar.measure(
+                    View.MeasureSpec.makeMeasureSpec(taskViewSize.width(), View.MeasureSpec.EXACTLY),
+                    View.MeasureSpec.makeMeasureSpec(taskBarHeight, View.MeasureSpec.EXACTLY));
+            mHeaderBar.layout(0, 0, taskViewSize.width(), taskBarHeight);
+        }
     }
 
     /** Prepares the search bar app widget */
@@ -607,30 +618,27 @@
      */
     ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
             TaskStack stack, TaskStackView stackView) {
+
         // Update the destination rect
         Task toTask = new Task();
         TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
                 topTask.id, toTask);
-        if (toTransform != null && toTask.key != null) {
-            Rect toTaskRect = toTransform.rect;
-            int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
-            int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
-            Bitmap thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
-                    Bitmap.Config.ARGB_8888);
-            if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
-                thumbnail.eraseColor(0xFFff0000);
-            } else {
-                Canvas c = new Canvas(thumbnail);
-                c.scale(toTransform.scale, toTransform.scale);
-                mHeaderBar.rebindToTask(toTask);
-                mHeaderBar.draw(c);
-                c.setBitmap(null);
-            }
-            Bitmap thumbnailImmutable = thumbnail.createAshmemBitmap();
-
+        Rect toTaskRect = toTransform.rect;
+        Bitmap thumbnail;
+        if (mThumbnailTransitionBitmapCacheKey != null
+                && mThumbnailTransitionBitmapCacheKey.key != null
+                && mThumbnailTransitionBitmapCacheKey.key.equals(toTask.key)) {
+            thumbnail = mThumbnailTransitionBitmapCache;
+            mThumbnailTransitionBitmapCacheKey = null;
+            mThumbnailTransitionBitmapCache = null;
+        } else {
+            preloadIcon(topTask);
+            thumbnail = drawThumbnailTransitionBitmap(toTask, toTransform);
+        }
+        if (thumbnail != null) {
             mStartAnimationTriggered = false;
             return ActivityOptions.makeThumbnailAspectScaleDownAnimation(mDummyStackView,
-                    thumbnailImmutable, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
+                    thumbnail, toTaskRect.left, toTaskRect.top, toTaskRect.width(),
                     toTaskRect.height(), mHandler, this);
         }
 
@@ -638,6 +646,72 @@
         return getUnknownTransitionActivityOptions();
     }
 
+    /**
+     * Preloads the icon of a task.
+     */
+    void preloadIcon(ActivityManager.RunningTaskInfo task) {
+
+        // Ensure that we load the running task's icon
+        RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
+        launchOpts.runningTaskId = task.id;
+        launchOpts.loadThumbnails = false;
+        launchOpts.onlyLoadForCache = true;
+        RecentsTaskLoader.getInstance().loadTasks(mContext, sInstanceLoadPlan, launchOpts);
+    }
+
+    /**
+     * Caches the header thumbnail used for a window animation asynchronously into
+     * {@link #mThumbnailTransitionBitmapCache}.
+     */
+    void preCacheThumbnailTransitionBitmapAsync(ActivityManager.RunningTaskInfo topTask,
+            TaskStack stack, TaskStackView stackView, boolean isTopTaskHome) {
+        preloadIcon(topTask);
+
+        // Update the destination rect
+        mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
+        final Task toTask = new Task();
+        final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
+                topTask.id, toTask);
+        new AsyncTask<Void, Void, Bitmap>() {
+            @Override
+            protected Bitmap doInBackground(Void... params) {
+                return drawThumbnailTransitionBitmap(toTask, toTransform);
+            }
+
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                mThumbnailTransitionBitmapCache = bitmap;
+                mThumbnailTransitionBitmapCacheKey = toTask;
+            }
+        }.execute();
+    }
+
+    /**
+     * Draws the header of a task used for the window animation into a bitmap.
+     */
+    Bitmap drawThumbnailTransitionBitmap(Task toTask, TaskViewTransform toTransform) {
+        if (toTransform != null && toTask.key != null) {
+            Bitmap thumbnail;
+            synchronized (mHeaderBarLock) {
+                int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
+                int toHeaderHeight = (int) (mHeaderBar.getMeasuredHeight() * toTransform.scale);
+                thumbnail = Bitmap.createBitmap(toHeaderWidth, toHeaderHeight,
+                        Bitmap.Config.ARGB_8888);
+                if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+                    thumbnail.eraseColor(0xFFff0000);
+                } else {
+                    Canvas c = new Canvas(thumbnail);
+                    c.scale(toTransform.scale, toTransform.scale);
+                    mHeaderBar.rebindToTask(toTask);
+                    mHeaderBar.draw(c);
+                    c.setBitmap(null);
+                }
+            }
+            return thumbnail.createAshmemBitmap();
+        }
+        return null;
+    }
+
     /** Returns the transition rect for the given task id. */
     TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
             int runningTaskId, Task runningTaskOut) {
@@ -694,7 +768,9 @@
             return;
         }
 
-        loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+        if (!sInstanceLoadPlan.hasTasks()) {
+            loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
+        }
         ArrayList<TaskStack> stacks = sInstanceLoadPlan.getAllTaskStacks();
         TaskStack stack = stacks.get(0);
 
@@ -706,12 +782,6 @@
         boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks;
 
         if (useThumbnailTransition) {
-            // Ensure that we load the running task's icon
-            RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
-            launchOpts.runningTaskId = topTask.id;
-            launchOpts.loadThumbnails = false;
-            launchOpts.onlyLoadForCache = true;
-            loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts);
 
             // Try starting with a thumbnail transition
             ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index ad97f91..9404dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -441,6 +441,10 @@
         if (mConfig.launchedHasConfigurationChanged) {
             onEnterAnimationTriggered();
         }
+
+        if (!mConfig.launchedHasConfigurationChanged) {
+            mRecentsView.disableLayersForOneFrame();
+        }
     }
 
     @Override
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 cec613c..1a21d8a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -431,6 +431,13 @@
         return false;
     }
 
+    public void disableLayersForOneFrame() {
+        List<TaskStackView> stackViews = getTaskStackViews();
+        for (int i = 0; i < stackViews.size(); i++) {
+            stackViews.get(i).disableLayersForOneFrame();
+        }
+    }
+
     /**** TaskStackView.TaskStackCallbacks Implementation ****/
 
     @Override
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 5f151e8..5711cd6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.Context;
+import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.view.LayoutInflater;
@@ -63,7 +64,6 @@
 
         public void onTaskResize(Task t);
     }
-
     RecentsConfiguration mConfig;
 
     TaskStack mStack;
@@ -81,7 +81,6 @@
     boolean mDismissAllButtonAnimating;
     int mFocusedTaskIndex = -1;
     int mPrevAccessibilityFocusedIndex = -1;
-
     // Optimizations
     int mStackViewsAnimationDuration;
     boolean mStackViewsDirty = true;
@@ -99,6 +98,7 @@
     ArrayList<TaskView> mTaskViews = new ArrayList<TaskView>();
     List<TaskView> mImmutableTaskViews = new ArrayList<TaskView>();
     LayoutInflater mInflater;
+    boolean mLayersDisabled;
 
     // A convenience update listener to request updating clipping of tasks
     ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
@@ -375,7 +375,9 @@
 
                 if (tv == null) {
                     tv = mViewPool.pickUpViewFromPool(task, task);
-
+                    if (mLayersDisabled) {
+                        tv.disableLayersForOneFrame();
+                    }
                     if (mStackViewsAnimationDuration > 0) {
                         // For items in the list, put them in start animating them from the
                         // approriate ends of the list where they are expected to appear
@@ -1031,6 +1033,20 @@
         mUIDozeTrigger.poke();
     }
 
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        mLayersDisabled = false;
+        super.dispatchDraw(canvas);
+    }
+
+    public void disableLayersForOneFrame() {
+        mLayersDisabled = true;
+        List<TaskView> taskViews = getTaskViews();
+        for (int i = 0; i < taskViews.size(); i++) {
+            taskViews.get(i).disableLayersForOneFrame();
+        }
+    }
+
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
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 01ed08a..c27e3a8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -650,6 +650,10 @@
         }
     }
 
+    public void disableLayersForOneFrame() {
+        mHeaderView.disableLayersForOneFrame();
+    }
+
     /**** TaskCallbacks Implementation ****/
 
     /** Binds this task view to the task */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index f397bc3..09e9839 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -80,6 +80,8 @@
     Paint mDimLayerPaint = new Paint();
     PorterDuffColorFilter mDimColorFilter = new PorterDuffColorFilter(0, PorterDuff.Mode.SRC_ATOP);
 
+    boolean mLayersDisabled;
+
     public TaskViewHeader(Context context) {
         this(context, null);
     }
@@ -172,7 +174,9 @@
     void setDimAlpha(int alpha) {
         mDimColorFilter.setColor(Color.argb(alpha, 0, 0, 0));
         mDimLayerPaint.setColorFilter(mDimColorFilter);
-        setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+        if (!mLayersDisabled) {
+            setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+        }
     }
 
     /** Returns the secondary color for a primary color. */
@@ -305,6 +309,28 @@
         return new int[] {};
     }
 
+    @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (mLayersDisabled) {
+            mLayersDisabled = false;
+            postOnAnimation(new Runnable() {
+                @Override
+                public void run() {
+                    mLayersDisabled = false;
+                    setLayerType(LAYER_TYPE_HARDWARE, mDimLayerPaint);
+                }
+            });
+        }
+    }
+
+    public void disableLayersForOneFrame() {
+        mLayersDisabled = true;
+
+        // Disable layer for a frame so we can draw our first frame faster.
+        setLayerType(LAYER_TYPE_NONE, null);
+    }
+
     /** Notifies the associated TaskView has been focused. */
     void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
         // If we are not animating the visible state, just return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index fa172a4..f05ac1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1030,9 +1030,7 @@
 
     @Override
     public void toggleRecentApps() {
-        int msg = MSG_TOGGLE_RECENTS_APPS;
-        mHandler.removeMessages(msg);
-        mHandler.sendEmptyMessage(msg);
+        toggleRecents();
     }
 
     @Override