Refactoring loading of recent apps

- fix bugs 5278690 and 5432097
- no longer forcing a reload when screen is rotated
- moving recents loading code into a seaprate class
- changing variable names to use "Task" rather than "Activity" in Recent Apps

Change-Id: Ib0c8c5d537cf9d46d65b2ccb790015b601bb1bf1
diff --git a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
index fbf00d2..886a14d 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/Choreographer.java
@@ -121,6 +121,7 @@
         createAnimation(appearing);
 
         mContentView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        mContentView.buildLayer();
         mContentAnim.start();
 
         mVisible = appearing;
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
new file mode 100644
index 0000000..47aa849
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2011 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.recent;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.LruCache;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import com.android.systemui.statusbar.tablet.TabletStatusBar;
+
+public class RecentTasksLoader {
+    static final String TAG = "RecentTasksLoader";
+    static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
+
+    private static final int DISPLAY_TASKS = 20;
+    private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
+
+    private Context mContext;
+    private RecentsPanelView mRecentsPanel;
+
+    private AsyncTask<Void, Integer, Void> mThumbnailLoader;
+    private final Handler mHandler;
+
+    private int mIconDpi;
+    private Bitmap mDefaultThumbnailBackground;
+
+    public RecentTasksLoader(Context context) {
+        mContext = context;
+
+        final Resources res = context.getResources();
+
+        // get the icon size we want -- on tablets, we use bigger icons
+        boolean isTablet = res.getBoolean(R.bool.config_recents_interface_for_tablets);
+        int density = res.getDisplayMetrics().densityDpi;
+        if (isTablet) {
+            if (density == DisplayMetrics.DENSITY_LOW) {
+                mIconDpi = DisplayMetrics.DENSITY_MEDIUM;
+            } else if (density == DisplayMetrics.DENSITY_MEDIUM) {
+                mIconDpi = DisplayMetrics.DENSITY_HIGH;
+            } else if (density == DisplayMetrics.DENSITY_HIGH) {
+                mIconDpi = DisplayMetrics.DENSITY_XHIGH;
+            } else if (density == DisplayMetrics.DENSITY_XHIGH) {
+                // We'll need to use a denser icon, or some sort of a mipmap
+                mIconDpi = DisplayMetrics.DENSITY_XHIGH;
+            }
+        } else {
+            mIconDpi = res.getDisplayMetrics().densityDpi;
+        }
+        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 color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
+
+        mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas c = new Canvas(mDefaultThumbnailBackground);
+        c.drawColor(color);
+
+        // If we're using the cache, begin listening to the activity manager for
+        // updated thumbnails
+        final ActivityManager am = (ActivityManager)
+                mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
+        mHandler = new Handler();
+    }
+
+    public void setRecentsPanel(RecentsPanelView recentsPanel) {
+        mRecentsPanel = recentsPanel;
+    }
+
+    // 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,
+            ComponentName origActivity, CharSequence description, ActivityInfo homeInfo) {
+        Intent intent = new Intent(baseIntent);
+        if (origActivity != null) {
+            intent.setComponent(origActivity);
+        }
+        final PackageManager pm = mContext.getPackageManager();
+        if (homeInfo == null) {
+            homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+            .resolveActivityInfo(pm, 0);
+        }
+
+        intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                | Intent.FLAG_ACTIVITY_NEW_TASK);
+        final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+        if (resolveInfo != null) {
+            final ActivityInfo info = resolveInfo.activityInfo;
+            final String title = info.loadLabel(pm).toString();
+            Drawable icon = getFullResIcon(resolveInfo, pm);
+
+            if (title != null && title.length() > 0 && icon != null) {
+                if (DEBUG) Log.v(TAG, "creating activity desc for id="
+                        + persistentTaskId + ", label=" + title);
+
+                TaskDescription item = new TaskDescription(taskId,
+                        persistentTaskId, resolveInfo, baseIntent, info.packageName,
+                        description);
+                item.setLabel(title);
+                item.setIcon(icon);
+
+                // Don't load the current home activity.
+                if (homeInfo != null
+                        && homeInfo.packageName.equals(intent.getComponent().getPackageName())
+                        && homeInfo.name.equals(intent.getComponent().getClassName())) {
+                    return null;
+                }
+
+                return item;
+            } else {
+                if (DEBUG) Log.v(TAG, "SKIPPING item " + persistentTaskId);
+            }
+        }
+        return null;
+    }
+
+    void loadThumbnail(TaskDescription td) {
+        final ActivityManager am = (ActivityManager)
+                mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(td.persistentTaskId);
+
+        if (DEBUG) Log.v(TAG, "Loaded bitmap for task "
+                + td + ": " + thumbs.mainThumbnail);
+        synchronized (td) {
+            if (thumbs != null && thumbs.mainThumbnail != null) {
+                td.setThumbnail(thumbs.mainThumbnail);
+            } else {
+                td.setThumbnail(mDefaultThumbnailBackground);
+            }
+        }
+    }
+
+    Drawable getFullResDefaultActivityIcon() {
+        return getFullResIcon(Resources.getSystem(),
+                com.android.internal.R.mipmap.sym_def_app_icon);
+    }
+
+    Drawable getFullResIcon(Resources resources, int iconId) {
+        try {
+            return resources.getDrawableForDensity(iconId, mIconDpi);
+        } catch (Resources.NotFoundException e) {
+            return getFullResDefaultActivityIcon();
+        }
+    }
+
+    private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
+        Resources resources;
+        try {
+            resources = packageManager.getResourcesForApplication(
+                    info.activityInfo.applicationInfo);
+        } catch (PackageManager.NameNotFoundException e) {
+            resources = null;
+        }
+        if (resources != null) {
+            int iconId = info.activityInfo.getIconResource();
+            if (iconId != 0) {
+                return getFullResIcon(resources, iconId);
+            }
+        }
+        return getFullResDefaultActivityIcon();
+    }
+
+    public void cancelLoadingThumbnails() {
+        if (mThumbnailLoader != null) {
+            mThumbnailLoader.cancel(false);
+            mThumbnailLoader = null;
+        }
+    }
+
+    // return a snapshot of the current list of recent apps
+    ArrayList<TaskDescription> getRecentTasks() {
+        cancelLoadingThumbnails();
+
+        ArrayList<TaskDescription> tasks = new ArrayList<TaskDescription>();
+        final PackageManager pm = mContext.getPackageManager();
+        final ActivityManager am = (ActivityManager)
+                mContext.getSystemService(Context.ACTIVITY_SERVICE);
+
+        final List<ActivityManager.RecentTaskInfo> recentTasks =
+                am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
+
+        ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+                    .resolveActivityInfo(pm, 0);
+
+        HashSet<Integer> recentTasksToKeepInCache = new HashSet<Integer>();
+        int numTasks = recentTasks.size();
+
+        // skip the first task - assume it's either the home screen or the current activity.
+        final int first = 1;
+        recentTasksToKeepInCache.add(recentTasks.get(0).persistentId);
+        for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
+            final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
+
+            TaskDescription item = createTaskDescription(recentInfo.id,
+                    recentInfo.persistentId, recentInfo.baseIntent,
+                    recentInfo.origActivity, recentInfo.description, homeInfo);
+
+            if (item != null) {
+                tasks.add(item);
+                ++index;
+            }
+        }
+
+        // when we're not using the TaskDescription cache, we load the thumbnails in the
+        // background
+        loadThumbnailsInBackground(new ArrayList<TaskDescription>(tasks));
+        return tasks;
+    }
+
+    private void loadThumbnailsInBackground(final ArrayList<TaskDescription> descriptions) {
+        if (descriptions.size() > 0) {
+            if (DEBUG) Log.v(TAG, "Showing " + descriptions.size() + " tasks");
+            loadThumbnail(descriptions.get(0));
+            if (descriptions.size() > 1) {
+                mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
+                    @Override
+                    protected void onProgressUpdate(Integer... values) {
+                        final TaskDescription td = descriptions.get(values[0]);
+                        if (!isCancelled()) {
+                            mRecentsPanel.onTaskThumbnailLoaded(td);
+                        }
+                        // This is to prevent the loader thread from getting ahead
+                        // of our UI updates.
+                        mHandler.post(new Runnable() {
+                            @Override public void run() {
+                                synchronized (td) {
+                                    td.notifyAll();
+                                }
+                            }
+                        });
+                    }
+
+                    @Override
+                    protected Void doInBackground(Void... params) {
+                        final int origPri = Process.getThreadPriority(Process.myTid());
+                        Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
+                        long nextTime = SystemClock.uptimeMillis();
+                        for (int i=1; i<descriptions.size(); i++) {
+                            TaskDescription td = descriptions.get(i);
+                            loadThumbnail(td);
+                            long now = SystemClock.uptimeMillis();
+                            nextTime += 150;
+                            if (nextTime > now) {
+                                try {
+                                    Thread.sleep(nextTime-now);
+                                } catch (InterruptedException e) {
+                                }
+                            }
+
+                            if (isCancelled()) {
+                                break;
+                            }
+                            synchronized (td) {
+                                publishProgress(i);
+                                try {
+                                    td.wait(500);
+                                } catch (InterruptedException e) {
+                                }
+                            }
+                        }
+                        Process.setThreadPriority(origPri);
+                        return null;
+                    }
+                };
+                mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+            }
+        }
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
index 58af255..f971d2d 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsHorizontalScrollView.java
@@ -32,14 +32,14 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
-import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter;
+import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
 
 public class RecentsHorizontalScrollView extends HorizontalScrollView
     implements SwipeHelper.Callback {
     private static final String TAG = RecentsPanelView.TAG;
     private static final boolean DEBUG = RecentsPanelView.DEBUG;
     private LinearLayout mLinearLayout;
-    private ActivityDescriptionAdapter mAdapter;
+    private TaskDescriptionAdapter mAdapter;
     private RecentsCallback mCallback;
     protected int mLastScrollPosition;
     private SwipeHelper mSwipeHelper;
@@ -304,7 +304,7 @@
         }
     }
 
-    public void setAdapter(ActivityDescriptionAdapter adapter) {
+    public void setAdapter(TaskDescriptionAdapter adapter) {
         mAdapter = adapter;
         mAdapter.registerDataSetObserver(new DataSetObserver() {
             public void onChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index e3f5cdb..5b4c33e 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -17,32 +17,20 @@
 package com.android.systemui.recent;
 
 import java.util.ArrayList;
-import java.util.List;
 
 import android.animation.Animator;
 import android.animation.LayoutTransition;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 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.Shader.TileMode;
 import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Process;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.AttributeSet;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -72,66 +60,24 @@
         implements OnItemClickListener, RecentsCallback, StatusBarPanel, Animator.AnimatorListener {
     static final String TAG = "RecentsPanelView";
     static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false;
-    private static final int DISPLAY_TASKS = 20;
-    private static final int MAX_TASKS = DISPLAY_TASKS + 1; // allow extra for non-apps
+    private Context mContext;
     private StatusBar mBar;
-    private ArrayList<ActivityDescription> mActivityDescriptions;
-    private AsyncTask<Void, Integer, Void> mThumbnailLoader;
-    private int mIconDpi;
     private View mRecentsScrim;
     private View mRecentsGlowView;
     private View mRecentsNoApps;
     private ViewGroup mRecentsContainer;
-    private Bitmap mDefaultThumbnailBackground;
 
     private boolean mShowing;
     private Choreographer mChoreo;
     private View mRecentsDismissButton;
-    private ActivityDescriptionAdapter mListAdapter;
-    private final Handler mHandler = new Handler();
 
-    /* package */ final class ActivityDescription {
-        final ActivityManager.RecentTaskInfo recentTaskInfo;
-        final ResolveInfo resolveInfo;
-        final int taskId; // application task id for curating apps
-        final int persistentTaskId; // persistent id
-        final Intent intent; // launch intent for application
-        final String packageName; // used to override animations (see onClick())
-        final int position; // position in list
+    private RecentTasksLoader mRecentTasksLoader;
+    private ArrayList<TaskDescription> mRecentTaskDescriptions;
+    private TaskDescriptionAdapter mListAdapter;
+    private int mThumbnailWidth;
 
-        Matrix matrix; // arbitrary rotation matrix to correct orientation
-
-        private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail()
-        private Drawable mIcon; // application package icon
-        private CharSequence mLabel; // application package label
-
-        public ActivityDescription(ActivityManager.RecentTaskInfo _recentInfo,
-                ResolveInfo _resolveInfo, Intent _intent,
-                int _pos, String _packageName) {
-            recentTaskInfo = _recentInfo;
-            resolveInfo = _resolveInfo;
-            intent = _intent;
-            taskId = _recentInfo.id;
-            persistentTaskId = _recentInfo.persistentId;
-            position = _pos;
-            packageName = _packageName;
-        }
-
-        public CharSequence getLabel() {
-            return mLabel;
-        }
-
-        public Drawable getIcon() {
-            return mIcon;
-        }
-
-        public void setThumbnail(Bitmap thumbnail) {
-            mThumbnail = thumbnail;
-        }
-
-        public Bitmap getThumbnail() {
-            return mThumbnail;
-        }
+    public void setRecentTasksLoader(RecentTasksLoader loader) {
+        mRecentTasksLoader = loader;
     }
 
     private final class OnLongClickDelegate implements View.OnLongClickListener {
@@ -150,18 +96,18 @@
         ImageView iconView;
         TextView labelView;
         TextView descriptionView;
-        ActivityDescription activityDescription;
+        TaskDescription taskDescription;
     }
 
-    /* package */ final class ActivityDescriptionAdapter extends BaseAdapter {
+    /* package */ final class TaskDescriptionAdapter extends BaseAdapter {
         private LayoutInflater mInflater;
 
-        public ActivityDescriptionAdapter(Context context) {
+        public TaskDescriptionAdapter(Context context) {
             mInflater = LayoutInflater.from(context);
         }
 
         public int getCount() {
-            return mActivityDescriptions != null ? mActivityDescriptions.size() : 0;
+            return mRecentTaskDescriptions != null ? mRecentTaskDescriptions.size() : 0;
         }
 
         public Object getItem(int position) {
@@ -189,18 +135,15 @@
                 holder = (ViewHolder) convertView.getTag();
             }
 
-            // activityId is reverse since most recent appears at the bottom...
-            final int activityId = mActivityDescriptions.size() - position - 1;
+            // index is reverse since most recent appears at the bottom...
+            final int index = mRecentTaskDescriptions.size() - position - 1;
 
-            final ActivityDescription activityDescription = mActivityDescriptions.get(activityId);
-            holder.thumbnailViewImage.setImageBitmap(activityDescription.getThumbnail());
-            holder.iconView.setImageDrawable(activityDescription.getIcon());
-            holder.labelView.setText(activityDescription.getLabel());
-            holder.descriptionView.setText(activityDescription.recentTaskInfo.description);
-            holder.thumbnailView.setTag(activityDescription);
+            final TaskDescription taskDescription = mRecentTaskDescriptions.get(index);
+            applyTaskDescription(holder, taskDescription, false);
+
+            holder.thumbnailView.setTag(taskDescription);
             holder.thumbnailView.setOnLongClickListener(new OnLongClickDelegate(convertView));
-            holder.thumbnailView.setContentDescription(activityDescription.getLabel());
-            holder.activityDescription = activityDescription;
+            holder.taskDescription = taskDescription;
 
             return convertView;
         }
@@ -226,14 +169,19 @@
     }
 
     public void show(boolean show, boolean animate) {
+        show(show, animate, null);
+    }
+
+    public void show(boolean show, boolean animate,
+            ArrayList<TaskDescription> recentTaskDescriptions) {
         if (show) {
             // Need to update list of recent apps before we set visibility so this view's
             // content description is updated before it gets focus for TalkBack mode
-            refreshApplicationList();
+            refreshRecentTasksList(recentTaskDescriptions);
 
             // if there are no apps, either bring up a "No recent apps" message, or just
             // quit early
-            boolean noApps = (mActivityDescriptions.size() == 0);
+            boolean noApps = (mRecentTaskDescriptions.size() == 0);
             if (mRecentsNoApps != null) { // doesn't exist on large devices
                 mRecentsNoApps.setVisibility(noApps ? View.VISIBLE : View.INVISIBLE);
             } else {
@@ -242,6 +190,8 @@
                     return;
                 }
             }
+        } else {
+            mRecentTasksLoader.cancelLoadingThumbnails();
         }
         if (animate) {
             if (mShowing != show) {
@@ -255,6 +205,7 @@
             mShowing = show;
             setVisibility(show ? View.VISIBLE : View.GONE);
             mChoreo.jumpTo(show);
+            onAnimationEnd(null);
         }
         if (show) {
             setFocusable(true);
@@ -298,6 +249,9 @@
             createCustomAnimations(transitioner);
         } else {
             ((ViewGroup)mRecentsContainer).setLayoutTransition(null);
+            // Clear memory used by screenshots
+            mRecentTaskDescriptions.clear();
+            mListAdapter.notifyDataSetInvalidated();
         }
     }
 
@@ -349,21 +303,13 @@
 
     public RecentsPanelView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mContext = context;
+        updateValuesFromResources();
+    }
 
-        Resources res = context.getResources();
-        boolean xlarge = (res.getConfiguration().screenLayout
-                & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE;
-
-        mIconDpi = xlarge ? DisplayMetrics.DENSITY_HIGH : res.getDisplayMetrics().densityDpi;
-
-        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 color = res.getColor(R.drawable.status_bar_recents_app_thumbnail_background);
-
-        // Render the default thumbnail background
-        mDefaultThumbnailBackground = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(mDefaultThumbnailBackground);
-        c.drawColor(color);
+    public void updateValuesFromResources() {
+        mThumbnailWidth =
+            (int) mContext.getResources().getDimension(R.dimen.status_bar_recents_thumbnail_width);
     }
 
     @Override
@@ -371,7 +317,7 @@
         super.onFinishInflate();
         mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         mRecentsContainer = (ViewGroup) findViewById(R.id.recents_container);
-        mListAdapter = new ActivityDescriptionAdapter(mContext);
+        mListAdapter = new TaskDescriptionAdapter(mContext);
         if (mRecentsContainer instanceof RecentsHorizontalScrollView){
             RecentsHorizontalScrollView scrollView
                     = (RecentsHorizontalScrollView) mRecentsContainer;
@@ -427,122 +373,50 @@
         }
     }
 
-    Drawable getFullResDefaultActivityIcon() {
-        return getFullResIcon(Resources.getSystem(),
-                com.android.internal.R.mipmap.sym_def_app_icon);
-    }
 
-    Drawable getFullResIcon(Resources resources, int iconId) {
-        try {
-            return resources.getDrawableForDensity(iconId, mIconDpi);
-        } catch (Resources.NotFoundException e) {
-            return getFullResDefaultActivityIcon();
-        }
-    }
-
-    private Drawable getFullResIcon(ResolveInfo info, PackageManager packageManager) {
-        Resources resources;
-        try {
-            resources = packageManager.getResourcesForApplication(
-                    info.activityInfo.applicationInfo);
-        } catch (PackageManager.NameNotFoundException e) {
-            resources = null;
-        }
-        if (resources != null) {
-            int iconId = info.activityInfo.getIconResource();
-            if (iconId != 0) {
-                return getFullResIcon(resources, iconId);
+    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);
         }
-        return getFullResDefaultActivityIcon();
-    }
-
-    private ArrayList<ActivityDescription> getRecentTasks() {
-        ArrayList<ActivityDescription> activityDescriptions = new ArrayList<ActivityDescription>();
-        final PackageManager pm = mContext.getPackageManager();
-        final ActivityManager am = (ActivityManager)
-                mContext.getSystemService(Context.ACTIVITY_SERVICE);
-
-        final List<ActivityManager.RecentTaskInfo> recentTasks =
-                am.getRecentTasks(MAX_TASKS, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
-
-        ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
-                    .resolveActivityInfo(pm, 0);
-
-        int numTasks = recentTasks.size();
-
-        // skip the first activity - assume it's either the home screen or the current app.
-        final int first = 1;
-        for (int i = first, index = 0; i < numTasks && (index < MAX_TASKS); ++i) {
-            final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
-
-            Intent intent = new Intent(recentInfo.baseIntent);
-            if (recentInfo.origActivity != null) {
-                intent.setComponent(recentInfo.origActivity);
+        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));
             }
-
-            // Skip the current home activity.
-            if (homeInfo != null
-                    && homeInfo.packageName.equals(intent.getComponent().getPackageName())
-                    && homeInfo.name.equals(intent.getComponent().getClassName())) {
-                continue;
-            }
-
-            intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
-                    | Intent.FLAG_ACTIVITY_NEW_TASK);
-            final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
-            if (resolveInfo != null) {
-                final ActivityInfo info = resolveInfo.activityInfo;
-                final String title = info.loadLabel(pm).toString();
-                Drawable icon = getFullResIcon(resolveInfo, pm);
-                if (title != null && title.length() > 0 && icon != null) {
-                    if (DEBUG) Log.v(TAG, "creating activity desc for id="
-                            + recentInfo.id + ", label=" + title);
-                    ActivityDescription item = new ActivityDescription(recentInfo,
-                            resolveInfo, intent, index, info.packageName);
-                    activityDescriptions.add(item);
-                    ++index;
-                } else {
-                    if (DEBUG) Log.v(TAG, "SKIPPING item " + recentInfo.id);
+            h.labelView.setVisibility(View.VISIBLE);
+        }
+        Bitmap thumbnail = td.getThumbnail();
+        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);
             }
         }
-        return activityDescriptions;
+        //h.descriptionView.setText(ad.description);
     }
 
-    ActivityDescription findActivityDescription(int id)
-    {
-        ActivityDescription desc = null;
-        for (int i = 0; i < mActivityDescriptions.size(); i++) {
-            ActivityDescription item = mActivityDescriptions.get(i);
-            if (item != null && item.taskId == id) {
-                desc = item;
-                break;
-            }
-        }
-        return desc;
-    }
-
-    void loadActivityDescription(ActivityDescription ad, int index) {
-        final ActivityManager am = (ActivityManager)
-                mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        final PackageManager pm = mContext.getPackageManager();
-        ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
-                ad.recentTaskInfo.persistentId);
-        CharSequence label = ad.resolveInfo.activityInfo.loadLabel(pm);
-        Drawable icon = getFullResIcon(ad.resolveInfo, pm);
-        if (DEBUG) Log.v(TAG, "Loaded bitmap for #" + index + " in "
-                + ad + ": " + thumbs.mainThumbnail);
-        synchronized (ad) {
-            ad.mLabel = label;
-            ad.mIcon = icon;
-            if (thumbs != null && thumbs.mainThumbnail != null) {
-                ad.setThumbnail(thumbs.mainThumbnail);
-            }
-        }
-    }
-
-    void applyActivityDescription(ActivityDescription ad, int index, boolean anim) {
+    void onTaskThumbnailLoaded(TaskDescription ad) {
         synchronized (ad) {
             if (mRecentsContainer != null) {
                 ViewGroup container = mRecentsContainer;
@@ -556,44 +430,8 @@
                     View v = container.getChildAt(i);
                     if (v.getTag() instanceof ViewHolder) {
                         ViewHolder h = (ViewHolder)v.getTag();
-                        if (h.activityDescription == ad) {
-                            if (DEBUG) Log.v(TAG, "Updating thumbnail #" + index + " in "
-                                    + h.activityDescription
-                                    + ": " + ad.getThumbnail());
-                            h.iconView.setImageDrawable(ad.getIcon());
-                            if (anim) {
-                                h.iconView.setAnimation(AnimationUtils.loadAnimation(
-                                        mContext, R.anim.recent_appear));
-                            }
-                            h.iconView.setVisibility(View.VISIBLE);
-                            h.labelView.setText(ad.getLabel());
-                            h.thumbnailView.setContentDescription(ad.getLabel());
-                            if (anim) {
-                                h.labelView.setAnimation(AnimationUtils.loadAnimation(
-                                        mContext, R.anim.recent_appear));
-                            }
-                            h.labelView.setVisibility(View.VISIBLE);
-                            Bitmap thumbnail = ad.getThumbnail();
-                            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 thumbnailViewWidth = h.thumbnailViewImage.getWidth();
-                                float scale = thumbnailViewWidth / thumbnail.getWidth();
-                                scaleMatrix.setScale(scale, scale);
-                                h.thumbnailViewImage.setScaleType(ScaleType.MATRIX);
-                                h.thumbnailViewImage.setImageMatrix(scaleMatrix);
-
-                                if (anim) {
-                                    h.thumbnailViewImage.setAnimation(AnimationUtils.loadAnimation(
-                                            mContext, R.anim.recent_appear));
-                                }
-                                h.thumbnailViewImage.setVisibility(View.VISIBLE);
-                            }
+                        if (h.taskDescription == ad) {
+                            applyTaskDescription(h, ad, true);
                         }
                     }
                 }
@@ -601,14 +439,28 @@
         }
     }
 
-    private void refreshApplicationList() {
-        if (mThumbnailLoader != null) {
-            mThumbnailLoader.cancel(false);
-            mThumbnailLoader = null;
+    private void refreshRecentTasksList(ArrayList<TaskDescription> recentTasksList) {
+        if (recentTasksList != null) {
+            mRecentTaskDescriptions = recentTasksList;
+        } else {
+            mRecentTaskDescriptions = mRecentTasksLoader.getRecentTasks();
         }
+        mListAdapter.notifyDataSetInvalidated();
+        updateUiElements(getResources().getConfiguration());
+    }
 
-        mActivityDescriptions = getRecentTasks();
-        int numRecentApps = mActivityDescriptions.size();
+    public ArrayList<TaskDescription> getRecentTasksList() {
+        return mRecentTaskDescriptions;
+    }
+
+    private void updateUiElements(Configuration config) {
+        final int items = mRecentTaskDescriptions.size();
+
+        mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
+        mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
+
+        // Set description for accessibility
+        int numRecentApps = mRecentTaskDescriptions.size();
         String recentAppsAccessibilityDescription;
         if (numRecentApps == 0) {
             recentAppsAccessibilityDescription =
@@ -618,81 +470,10 @@
                 R.plurals.status_bar_accessibility_recent_apps, numRecentApps, numRecentApps);
         }
         setContentDescription(recentAppsAccessibilityDescription);
-        for (ActivityDescription ad : mActivityDescriptions) {
-            ad.setThumbnail(mDefaultThumbnailBackground);
-        }
-        mListAdapter.notifyDataSetInvalidated();
-        if (mActivityDescriptions.size() > 0) {
-            if (DEBUG) Log.v(TAG, "Showing " + mActivityDescriptions.size() + " apps");
-            updateUiElements(getResources().getConfiguration());
-            final ArrayList<ActivityDescription> descriptions =
-                new ArrayList<ActivityDescription>(mActivityDescriptions);
-            loadActivityDescription(descriptions.get(0), 0);
-            applyActivityDescription(descriptions.get(0), 0, false);
-            if (descriptions.size() > 1) {
-                mThumbnailLoader = new AsyncTask<Void, Integer, Void>() {
-                    @Override
-                    protected void onProgressUpdate(Integer... values) {
-                        final ActivityDescription ad = descriptions.get(values[0]);
-                        if (!isCancelled()) {
-                            applyActivityDescription(ad, values[0], true);
-                        }
-                        // This is to prevent the loader thread from getting ahead
-                        // of our UI updates.
-                        mHandler.post(new Runnable() {
-                            @Override public void run() {
-                                synchronized (ad) {
-                                    ad.notifyAll();
-                                }
-                            }
-                        });
-                    }
-
-                    @Override
-                    protected Void doInBackground(Void... params) {
-                        final int origPri = Process.getThreadPriority(Process.myTid());
-                        Process.setThreadPriority(Process.THREAD_GROUP_BG_NONINTERACTIVE);
-                        long nextTime = SystemClock.uptimeMillis();
-                        for (int i=1; i<descriptions.size(); i++) {
-                            ActivityDescription ad = descriptions.get(i);
-                            loadActivityDescription(ad, i);
-                            long now = SystemClock.uptimeMillis();
-                            nextTime += 150;
-                            if (nextTime > now) {
-                                try {
-                                    Thread.sleep(nextTime-now);
-                                } catch (InterruptedException e) {
-                                }
-                            }
-                            if (isCancelled()) {
-                                break;
-                            }
-                            synchronized (ad) {
-                                publishProgress(i);
-                                try {
-                                    ad.wait(500);
-                                } catch (InterruptedException e) {
-                                }
-                            }
-                        }
-                        Process.setThreadPriority(origPri);
-                        return null;
-                    }
-                };
-                mThumbnailLoader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
-            }
-        }
-    }
-
-    private void updateUiElements(Configuration config) {
-        final int items = mActivityDescriptions.size();
-
-        mRecentsContainer.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
-        mRecentsGlowView.setVisibility(items > 0 ? View.VISIBLE : View.GONE);
     }
 
     public void handleOnClick(View view) {
-        ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
+        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
         final Context context = view.getContext();
         final ActivityManager am = (ActivityManager)
                 context.getSystemService(Context.ACTIVITY_SERVICE);
@@ -714,14 +495,14 @@
     }
 
     public void handleSwipe(View view) {
-        ActivityDescription ad = ((ViewHolder) view.getTag()).activityDescription;
+        TaskDescription ad = ((ViewHolder) view.getTag()).taskDescription;
         if (DEBUG) Log.v(TAG, "Jettison " + ad.getLabel());
-        mActivityDescriptions.remove(ad);
+        mRecentTaskDescriptions.remove(ad);
 
         // Handled by widget containers to enable LayoutTransitions properly
         // mListAdapter.notifyDataSetChanged();
 
-        if (mActivityDescriptions.size() == 0) {
+        if (mRecentTaskDescriptions.size() == 0) {
             hide(false);
         }
 
@@ -751,7 +532,7 @@
                 } else if (item.getItemId() == R.id.recent_inspect_item) {
                     ViewHolder viewHolder = (ViewHolder) selectedView.getTag();
                     if (viewHolder != null) {
-                        final ActivityDescription ad = viewHolder.activityDescription;
+                        final TaskDescription ad = viewHolder.taskDescription;
                         startApplicationDetailsActivity(ad.packageName);
                         mBar.animateCollapse();
                     } else {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
index 7853402..dc13092 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsVerticalScrollView.java
@@ -31,13 +31,13 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
-import com.android.systemui.recent.RecentsPanelView.ActivityDescriptionAdapter;
+import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
 
 public class RecentsVerticalScrollView extends ScrollView implements SwipeHelper.Callback {
     private static final String TAG = RecentsPanelView.TAG;
     private static final boolean DEBUG = RecentsPanelView.DEBUG;
     private LinearLayout mLinearLayout;
-    private ActivityDescriptionAdapter mAdapter;
+    private TaskDescriptionAdapter mAdapter;
     private RecentsCallback mCallback;
     protected int mLastScrollPosition;
     private SwipeHelper mSwipeHelper;
@@ -319,7 +319,7 @@
         }
     }
 
-    public void setAdapter(ActivityDescriptionAdapter adapter) {
+    public void setAdapter(TaskDescriptionAdapter adapter) {
         mAdapter = adapter;
         mAdapter.registerDataSetObserver(new DataSetObserver() {
             public void onChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
new file mode 100644
index 0000000..dcfd6d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2011 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.recent;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+
+public final class TaskDescription {
+    final ResolveInfo resolveInfo;
+    final int taskId; // application task id for curating apps
+    final int persistentTaskId; // persistent id
+    final Intent intent; // launch intent for application
+    final String packageName; // used to override animations (see onClick())
+    final CharSequence description;
+
+    private Bitmap mThumbnail; // generated by Activity.onCreateThumbnail()
+    private Drawable mIcon; // application package icon
+    private CharSequence mLabel; // application package label
+
+    public TaskDescription(int _taskId, int _persistentTaskId,
+            ResolveInfo _resolveInfo, Intent _intent,
+            String _packageName, CharSequence _description) {
+        resolveInfo = _resolveInfo;
+        intent = _intent;
+        taskId = _taskId;
+        persistentTaskId = _persistentTaskId;
+
+        description = _description;
+        packageName = _packageName;
+    }
+
+    // mark all these as locked?
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    public void setLabel(CharSequence label) {
+        mLabel = label;
+    }
+
+    public Drawable getIcon() {
+        return mIcon;
+    }
+
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+    }
+
+    public void setThumbnail(Bitmap thumbnail) {
+        mThumbnail = thumbnail;
+    }
+
+    public Bitmap getThumbnail() {
+        return mThumbnail;
+    }
+}
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 09ea6ad..e54de59 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -74,7 +74,9 @@
 import com.android.internal.statusbar.StatusBarNotification;
 
 import com.android.systemui.R;
+import com.android.systemui.recent.RecentTasksLoader;
 import com.android.systemui.recent.RecentsPanelView;
+import com.android.systemui.recent.TaskDescription;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBar;
 import com.android.systemui.statusbar.StatusBarIconView;
@@ -193,6 +195,7 @@
 
     // Recent apps
     private RecentsPanelView mRecentsPanel;
+    private RecentTasksLoader mRecentTasksLoader;
 
     // Tracking finger for opening/closing.
     int mEdgeBorder; // corresponds to R.dimen.status_bar_edge_ignore
@@ -363,6 +366,7 @@
         signalCluster.setNetworkController(mNetworkController);
 
         // Recents Panel
+        mRecentTasksLoader = new RecentTasksLoader(context);
         updateRecentsPanel();
 
         // receive broadcasts
@@ -399,16 +403,21 @@
     protected void updateRecentsPanel() {
         // Recents Panel
         boolean visible = false;
+        ArrayList<TaskDescription> recentTasksList = null;
         if (mRecentsPanel != null) {
             visible = mRecentsPanel.isShowing();
             WindowManagerImpl.getDefault().removeView(mRecentsPanel);
+            if (visible) {
+                recentTasksList = mRecentsPanel.getRecentTasksList();
+            }
         }
 
         // Provide RecentsPanelView with a temporary parent to allow layout params to work.
         LinearLayout tmpRoot = new LinearLayout(mContext);
         mRecentsPanel = (RecentsPanelView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_recent_panel, tmpRoot, false);
-
+        mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
+        mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
         mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
                 mRecentsPanel));
         mRecentsPanel.setVisibility(View.GONE);
@@ -417,7 +426,7 @@
         WindowManagerImpl.getDefault().addView(mRecentsPanel, lp);
         mRecentsPanel.setBar(this);
         if (visible) {
-            mRecentsPanel.show(true, false);
+            mRecentsPanel.show(true, false, recentTasksList);
         }
 
     }
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 ba52fb8..271e508 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -77,6 +77,7 @@
 import com.android.systemui.statusbar.policy.LocationController;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.Prefs;
+import com.android.systemui.recent.RecentTasksLoader;
 import com.android.systemui.recent.RecentsPanelView;
 
 public class TabletStatusBar extends StatusBar implements
@@ -179,6 +180,7 @@
     int mDisabled = 0;
 
     private RecentsPanelView mRecentsPanel;
+    private RecentTasksLoader mRecentTasksLoader;
     private InputMethodsPanel mInputMethodsPanel;
     private CompatModePanel mCompatModePanel;
 
@@ -299,12 +301,15 @@
         }
 
         // Recents Panel
+        mRecentTasksLoader = new RecentTasksLoader(context);
         mRecentsPanel = (RecentsPanelView) View.inflate(context,
                 R.layout.status_bar_recent_panel, null);
         mRecentsPanel.setVisibility(View.GONE);
         mRecentsPanel.setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK);
         mRecentsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_RECENTS_PANEL,
                 mRecentsPanel));
+        mRecentsPanel.setRecentTasksLoader(mRecentTasksLoader);
+        mRecentTasksLoader.setRecentsPanel(mRecentsPanel);
         mStatusBarView.setIgnoreChildren(2, mRecentButton, mRecentsPanel);
 
         lp = new WindowManager.LayoutParams(
@@ -392,6 +397,7 @@
         mNotificationPanelParams.height = getNotificationPanelHeight();
         WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
                 mNotificationPanelParams);
+        mRecentsPanel.updateValuesFromResources();
     }
 
     protected void loadDimens() {