Make Recent Apps faster

- start loading on touch down
- avoid unneeded calls to onLayout
- don't fade in thumbnails if they've been loaded before we show recent apps
- don't pause between loading thumbnails
- fade in thumbnails+shadow (rather than just thumbnail as before)

Change-Id: I6dd4be7f52f9e8b51284ae052614719db8e71dc5
diff --git a/packages/SystemUI/res/anim/recent_appear.xml b/packages/SystemUI/res/anim/recent_appear.xml
index 20fe052..4400d9d 100644
--- a/packages/SystemUI/res/anim/recent_appear.xml
+++ b/packages/SystemUI/res/anim/recent_appear.xml
@@ -16,5 +16,5 @@
 
 <alpha xmlns:android="http://schemas.android.com/apk/res/android"
     android:fromAlpha="0.0" android:toAlpha="1.0"
-    android:duration="@android:integer/config_mediumAnimTime"
+    android:duration="@android:integer/config_shortAnimTime"
     />
diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
index 83c4faf..2d76455 100644
--- a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml
@@ -39,11 +39,11 @@
             android:layout_marginTop="@dimen/status_bar_recents_thumbnail_top_margin"
             android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
             android:background="@drawable/recents_thumbnail_bg"
-            android:foreground="@drawable/recents_thumbnail_fg">
+            android:foreground="@drawable/recents_thumbnail_fg"
+            android:visibility="invisible">
             <ImageView android:id="@+id/app_thumbnail_image"
                 android:layout_width="@dimen/status_bar_recents_thumbnail_width"
                 android:layout_height="@dimen/status_bar_recents_thumbnail_height"
-                android:visibility="invisible"
             />
         </FrameLayout>
 
@@ -58,7 +58,6 @@
             android:maxHeight="@dimen/status_bar_recents_app_icon_max_height"
             android:scaleType="centerInside"
             android:adjustViewBounds="true"
-            android:visibility="invisible"
         />
 
         <TextView android:id="@+id/app_label"
@@ -74,7 +73,6 @@
             android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin"
             android:singleLine="true"
             android:ellipsize="marquee"
-            android:visibility="invisible"
             android:textColor="@color/status_bar_recents_app_label_color"
         />
 
diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
index 3d8b9d6..b653fcd 100644
--- a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml
@@ -52,11 +52,11 @@
             android:layout_toRightOf="@id/app_label"
             android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
             android:background="@drawable/recents_thumbnail_bg"
-            android:foreground="@drawable/recents_thumbnail_fg">
+            android:foreground="@drawable/recents_thumbnail_fg"
+            android:visibility="invisible">
             <ImageView android:id="@+id/app_thumbnail_image"
                 android:layout_width="@dimen/status_bar_recents_thumbnail_width"
                 android:layout_height="@dimen/status_bar_recents_thumbnail_height"
-                android:visibility="invisible"
             />
         </FrameLayout>
         <View android:id="@+id/recents_callout_line"
diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
index e6336718..18a31f7 100644
--- a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
+++ b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml
@@ -31,11 +31,11 @@
         android:layout_marginLeft="@dimen/status_bar_recents_thumbnail_left_margin"
         android:scaleType="center"
         android:background="@drawable/recents_thumbnail_bg"
-        android:foreground="@drawable/recents_thumbnail_fg">
+        android:foreground="@drawable/recents_thumbnail_fg"
+        android:visibility="invisible">
         <ImageView android:id="@+id/app_thumbnail_image"
             android:layout_width="@dimen/status_bar_recents_thumbnail_width"
             android:layout_height="@dimen/status_bar_recents_thumbnail_height"
-            android:visibility="invisible"
         />
         <ImageView android:id="@+id/app_icon"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index 47aa849..dcda9c2 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -86,8 +86,8 @@
         mIconDpi = isTablet ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
 
         // Render the default thumbnail background
-        int width = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_width);
-        int height = (int) res.getDimension(R.dimen.status_bar_recents_thumbnail_height);
+        int width = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+        int height = (int) res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
         int color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
 
         mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
@@ -106,6 +106,10 @@
         mRecentsPanel = recentsPanel;
     }
 
+    public Bitmap getDefaultThumbnail() {
+        return mDefaultThumbnailBackground;
+    }
+
     // Create an TaskDescription, returning null if the title or icon is null, or if it's the
     // home activity
     TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
@@ -278,7 +282,7 @@
                             TaskDescription td = descriptions.get(i);
                             loadThumbnail(td);
                             long now = SystemClock.uptimeMillis();
-                            nextTime += 150;
+                            nextTime += 0;
                             if (nextTime > now) {
                                 try {
                                     Thread.sleep(nextTime-now);
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index bd1fcfc..343b33514 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -37,6 +37,7 @@
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AnimationUtils;
@@ -57,8 +58,8 @@
 import com.android.systemui.statusbar.tablet.StatusBarPanel;
 import com.android.systemui.statusbar.tablet.TabletStatusBar;
 
-public class RecentsPanelView extends RelativeLayout
-        implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener {
+public class RecentsPanelView extends RelativeLayout implements OnItemClickListener, RecentsCallback,
+        StatusBarPanel, Animator.AnimatorListener, View.OnTouchListener {
     static final String TAG = "RecentsPanelView";
     static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
     private Context mContext;
@@ -74,6 +75,7 @@
 
     private RecentTasksLoader mRecentTasksLoader;
     private ArrayList<TaskDescription> mRecentTaskDescriptions;
+    private boolean mRecentTasksDirty = true;
     private TaskDescriptionAdapter mListAdapter;
     private int mThumbnailWidth;
 
@@ -94,6 +96,7 @@
     /* package */ final static class ViewHolder {
         View thumbnailView;
         ImageView thumbnailViewImage;
+        Bitmap thumbnailViewImageBitmap;
         ImageView iconView;
         TextView labelView;
         TextView descriptionView;
@@ -127,6 +130,10 @@
                 holder.thumbnailView = convertView.findViewById(R.id.app_thumbnail);
                 holder.thumbnailViewImage = (ImageView) convertView.findViewById(
                         R.id.app_thumbnail_image);
+                // If we set the default thumbnail now, we avoid an onLayout when we update
+                // the thumbnail later (if they both have the same dimensions)
+                updateThumbnail(holder, mRecentTasksLoader.getDefaultThumbnail(), false, false);
+
                 holder.iconView = (ImageView) convertView.findViewById(R.id.app_icon);
                 holder.labelView = (TextView) convertView.findViewById(R.id.app_label);
                 holder.descriptionView = (TextView) convertView.findViewById(R.id.app_description);
@@ -139,12 +146,15 @@
             // index is reverse since most recent appears at the bottom...
             final int index = mRecentTaskDescriptions.size() - position - 1;
 
-            final TaskDescription taskDescription = mRecentTaskDescriptions.get(index);
-            applyTaskDescription(holder, taskDescription, false);
+            final TaskDescription td = mRecentTaskDescriptions.get(index);
+            holder.iconView.setImageDrawable(td.getIcon());
+            holder.labelView.setText(td.getLabel());
+            holder.thumbnailView.setContentDescription(td.getLabel());
+            updateThumbnail(holder, td.getThumbnail(), true, false);
 
-            holder.thumbnailView.setTag(taskDescription);
+            holder.thumbnailView.setTag(td);
             holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
-            holder.taskDescription = taskDescription;
+            holder.taskDescription = td;
 
             return convertView;
         }
@@ -193,6 +203,7 @@
             }
         } else {
             mRecentTasksLoader.cancelLoadingThumbnails();
+            mRecentTasksDirty = true;
         }
         if (animate) {
             if (mShowing != show) {
@@ -250,9 +261,7 @@
             createCustomAnimations(transitioner);
         } else {
             ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
-            // Clear memory used by screenshots
-            mRecentTaskDescriptions.clear();
-            mListAdapter.notifyDataSetInvalidated();
+            clearRecentTasksList();
         }
     }
 
@@ -374,47 +383,33 @@
         }
     }
 
-
-    void applyTaskDescription(ViewHolder h, TaskDescription td, boolean anim) {
-        h.iconView.setImageDrawable(td.getIcon());
-        if (h.iconView.getVisibility() != View.VISIBLE) {
-            if (anim) {
-                h.iconView.setAnimation(AnimationUtils.loadAnimation(
-                        mContext, R.anim.recent_appear));
-            }
-            h.iconView.setVisibility(View.VISIBLE);
-        }
-        h.labelView.setText(td.getLabel());
-        h.thumbnailView.setContentDescription(td.getLabel());
-        if (h.labelView.getVisibility() != View.VISIBLE) {
-            if (anim) {
-                h.labelView.setAnimation(AnimationUtils.loadAnimation(
-                        mContext, R.anim.recent_appear));
-            }
-            h.labelView.setVisibility(View.VISIBLE);
-        }
-        Bitmap thumbnail = td.getThumbnail();
+    private void updateThumbnail(ViewHolder h, Bitmap thumbnail, boolean show, boolean anim) {
         if (thumbnail != null) {
             // Should remove the default image in the frame
             // that this now covers, to improve scrolling speed.
             // That can't be done until the anim is complete though.
             h.thumbnailViewImage.setImageBitmap(thumbnail);
-            // scale to fill up the full width
-            Matrix scaleMatrix = new Matrix();
-            float scale = mThumbnailWidth / (float) thumbnail.getWidth();
-            scaleMatrix.setScale(scale, scale);
-            h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
-            h.thumbnailViewImage.setImageMatrix(scaleMatrix);
-            if (h.thumbnailViewImage.getVisibility() != View.VISIBLE) {
-                if (anim) {
-                    h.thumbnailViewImage.setAnimation(
-                            AnimationUtils.loadAnimation(
-                                    mContext, R.anim.recent_appear));
-                }
-                h.thumbnailViewImage.setVisibility(View.VISIBLE);
+
+            // scale the image to fill the full width of the ImageView. do this only if
+            // we haven't set a bitmap before, or if the bitmap size has changed
+            if (h.thumbnailViewImageBitmap == null ||
+                h.thumbnailViewImageBitmap.getWidth() != thumbnail.getWidth() ||
+                h.thumbnailViewImageBitmap.getHeight() != thumbnail.getHeight()) {
+                Matrix scaleMatrix = new Matrix();
+                float scale = mThumbnailWidth / (float) thumbnail.getWidth();
+                scaleMatrix.setScale(scale, scale);
+                h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
+                h.thumbnailViewImage.setImageMatrix(scaleMatrix);
             }
+            if (show && h.thumbnailView.getVisibility() != View.VISIBLE) {
+                if (anim) {
+                    h.thumbnailView.setAnimation(
+                            AnimationUtils.loadAnimation(mContext, R.anim.recent_appear));
+                }
+                h.thumbnailView.setVisibility(View.VISIBLE);
+            }
+            h.thumbnailViewImageBitmap = thumbnail;
         }
-        //h.descriptionView.setText(ad.description);
     }
 
     void onTaskThumbnailLoaded(TaskDescription ad) {
@@ -432,7 +427,11 @@
                     if (v.getTag() instanceof ViewHolder) {
                         ViewHolder h = (ViewHolder)v.getTag();
                         if (h.taskDescription == ad) {
-                            applyTaskDescription(h, ad, true);
+                            // only fade in the thumbnail if recents is already visible-- we
+                            // show it immediately otherwise
+                            boolean animateShow = mShowing &&
+                                mRecentsGlowView.getAlpha() > ViewConfiguration.ALPHA_THRESHOLD;
+                            updateThumbnail(h, ad.getThumbnail(), true, animateShow);
                         }
                     }
                 }
@@ -440,14 +439,55 @@
         }
     }
 
-    private void refreshRecentTasksList(ArrayList<TaskDescription> recentTasksList) {
-        if (recentTasksList != null) {
-            mRecentTaskDescriptions = recentTasksList;
-        } else {
-            mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();
+    // additional optimization when we have sofware system buttons - start loading the recent
+    // tasks on touch down
+    @Override
+    public boolean onTouch(View v, MotionEvent ev) {
+        if (!mShowing) {
+            int action = ev.getAction() & MotionEvent.ACTION_MASK;
+            if (action == MotionEvent.ACTION_DOWN) {
+                // If we set our visibility to INVISIBLE here, we avoid an extra call to onLayout
+                // later when we become visible
+                setVisibility(INVISIBLE);
+                refreshRecentTasksList();
+            } else if (action == MotionEvent.ACTION_CANCEL) {
+                setVisibility(GONE);
+                clearRecentTasksList();
+            } else if (action == MotionEvent.ACTION_UP) {
+                if (!v.isPressed()) {
+                    setVisibility(GONE);
+                    clearRecentTasksList();
+                }
+            }
         }
-        mListAdapter.notifyDataSetInvalidated();
-        updateUiElements(getResources().getConfiguration());
+        return false;
+    }
+
+    public void clearRecentTasksList() {
+        // Clear memory used by screenshots
+        if (mRecentTaskDescriptions != null) {
+            mRecentTasksLoader.cancelLoadingThumbnails();
+            mRecentTaskDescriptions.clear();
+            mListAdapter.notifyDataSetInvalidated();
+            mRecentTasksDirty = true;
+        }
+    }
+
+    public void refreshRecentTasksList() {
+        refreshRecentTasksList(null);
+    }
+
+    private void refreshRecentTasksList(ArrayList<TaskDescription> recentTasksList) {
+        if (mRecentTasksDirty) {
+            if (recentTasksList != null) {
+                mRecentTaskDescriptions = recentTasksList;
+            } else {
+                mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();
+            }
+            mListAdapter.notifyDataSetInvalidated();
+            updateUiElements(getResources().getConfiguration());
+            mRecentTasksDirty = false;
+        }
     }
 
     public ArrayList<TaskDescription> getRecentTasksList() {
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 b724552..77a7f51 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -435,13 +435,18 @@
         }
     };
 
+    private void prepareNavigationBarView() {
+        mNavigationBarView.reorient();
+
+        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
+        mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPanel);
+    }
+
     // For small-screen devices (read: phones) that lack hardware navigation buttons
     private void addNavigationBar() {
         if (mNavigationBarView == null) return;
 
-        mNavigationBarView.reorient();
-
-        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
+        prepareNavigationBarView();
 
         WindowManagerImpl.getDefault().addView(
                 mNavigationBarView, getNavigationBarLayoutParams());
@@ -450,9 +455,7 @@
     private void repositionNavigationBar() {
         if (mNavigationBarView == null) return;
 
-        mNavigationBarView.reorient();
-
-        mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
+        prepareNavigationBarView();
 
         WindowManagerImpl.getDefault().updateViewLayout(
                 mNavigationBarView, getNavigationBarLayoutParams());
@@ -2007,8 +2010,8 @@
     }
 
     public void toggleRecentApps() {
-        int msg = (mRecentsPanel.getVisibility() == View.GONE)
-                ? MSG_OPEN_RECENTS_PANEL : MSG_CLOSE_RECENTS_PANEL;
+        int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
+                ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
         mHandler.removeMessages(msg);
         mHandler.sendEmptyMessage(msg);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index f0a10f3..de8226b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -587,6 +587,7 @@
 
         // Add the windows
         addPanelWindows();
+        mRecentButton.setOnTouchListener(mRecentsPanel);
 
         mPile = (ViewGroup)mNotificationPanel.findViewById(R.id.content);
         mPile.removeAllViews();
@@ -1805,8 +1806,8 @@
     }
 
     public void toggleRecentApps() {
-        int msg = (mRecentsPanel.getVisibility() == View.GONE)
-                ? MSG_OPEN_RECENTS_PANEL : MSG_CLOSE_RECENTS_PANEL;
+        int msg = (mRecentsPanel.getVisibility() == View.VISIBLE)
+                ? MSG_CLOSE_RECENTS_PANEL : MSG_OPEN_RECENTS_PANEL;
         mHandler.removeMessages(msg);
         mHandler.sendEmptyMessage(msg);
     }