Rework thumbnails in activity manager.

We now only keep a thumbnail for the task, not for each
activity.  However if you use FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
we will make a new secondary thumbnail for that series of
activities.  There is a new API for the app to get these
secondary thumbnails.

Also set a default thumbnail size for non-xlarge screens
so we have thumbnails on phones.  (We need some smarter
code in the platform for computing the actual thumbnail
dimensions of the current device).  And add a test app
to show recent tasks + thumbnails.

Change-Id: Ic36759f6635522118a2cb7f156662229a610c492
diff --git a/Android.mk b/Android.mk
index 3f52948..317668a 100644
--- a/Android.mk
+++ b/Android.mk
@@ -87,6 +87,7 @@
 	core/java/android/app/ISearchManagerCallback.aidl \
 	core/java/android/app/IServiceConnection.aidl \
 	core/java/android/app/IThumbnailReceiver.aidl \
+	core/java/android/app/IThumbnailRetriever.aidl \
 	core/java/android/app/ITransientNotification.aidl \
 	core/java/android/app/IUiModeManager.aidl \
 	core/java/android/app/IWallpaperManager.aidl \
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index b6581e9..a9c9d9c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -38,6 +38,7 @@
 import com.android.internal.app.IUsageStats;
 import com.android.internal.os.PkgUsageStats;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -396,9 +397,71 @@
     }
 
     /** @hide */
-    public Bitmap getTaskThumbnail(int id) throws SecurityException {
+    public static class TaskThumbnails implements Parcelable {
+        public Bitmap mainThumbnail;
+
+        public int numSubThumbbails;
+
+        /** @hide */
+        public IThumbnailRetriever retriever;
+
+        /** @hide Magic for ActivityManagerService.  Not marshalled */
+        public ArrayList<Bitmap> otherThumbnails;
+
+        public TaskThumbnails() {
+        }
+
+        public Bitmap getSubThumbnail(int index) {
+            try {
+                return retriever.getThumbnail(index);
+            } catch (RemoteException e) {
+                return null;
+            }
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            if (mainThumbnail != null) {
+                dest.writeInt(1);
+                mainThumbnail.writeToParcel(dest, 0);
+            } else {
+                dest.writeInt(0);
+            }
+            dest.writeInt(numSubThumbbails);
+            dest.writeStrongInterface(retriever);
+        }
+
+        public void readFromParcel(Parcel source) {
+            if (source.readInt() != 0) {
+                mainThumbnail = Bitmap.CREATOR.createFromParcel(source);
+            } else {
+                mainThumbnail = null;
+            }
+            numSubThumbbails = source.readInt();
+            retriever = IThumbnailRetriever.Stub.asInterface(source.readStrongBinder());
+        }
+
+        public static final Creator<TaskThumbnails> CREATOR = new Creator<TaskThumbnails>() {
+            public TaskThumbnails createFromParcel(Parcel source) {
+                return new TaskThumbnails(source);
+            }
+            public TaskThumbnails[] newArray(int size) {
+                return new TaskThumbnails[size];
+            }
+        };
+
+        private TaskThumbnails(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /** @hide */
+    public TaskThumbnails getTaskThumbnails(int id) throws SecurityException {
         try {
-            return ActivityManagerNative.getDefault().getTaskThumbnail(id);
+            return ActivityManagerNative.getDefault().getTaskThumbnails(id);
         } catch (RemoteException e) {
             // System dead, we will be dead too soon!
             return null;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 6426635..f51e4d0 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -442,10 +442,10 @@
             return true;
         }
         
-        case GET_TASK_THUMBNAIL_TRANSACTION: {
+        case GET_TASK_THUMBNAILS_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int id = data.readInt();
-            Bitmap bm = getTaskThumbnail(id);
+            ActivityManager.TaskThumbnails bm = getTaskThumbnails(id);
             reply.writeNoException();
             if (bm != null) {
                 reply.writeInt(1);
@@ -1831,16 +1831,16 @@
         reply.recycle();
         return list;
     }
-    public Bitmap getTaskThumbnail(int id) throws RemoteException {
+    public ActivityManager.TaskThumbnails getTaskThumbnails(int id) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeInt(id);
-        mRemote.transact(GET_TASK_THUMBNAIL_TRANSACTION, data, reply, 0);
+        mRemote.transact(GET_TASK_THUMBNAILS_TRANSACTION, data, reply, 0);
         reply.readException();
-        Bitmap bm = null;
+        ActivityManager.TaskThumbnails bm = null;
         if (reply.readInt() != 0) {
-            bm = Bitmap.CREATOR.createFromParcel(reply);
+            bm = ActivityManager.TaskThumbnails.CREATOR.createFromParcel(reply);
         }
         data.recycle();
         reply.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 61e6fc8..cd26a62 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -133,7 +133,7 @@
                          IThumbnailReceiver receiver) throws RemoteException;
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
             int flags) throws RemoteException;
-    public Bitmap getTaskThumbnail(int taskId) throws RemoteException;
+    public ActivityManager.TaskThumbnails getTaskThumbnails(int taskId) throws RemoteException;
     public List getServices(int maxNum, int flags) throws RemoteException;
     public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
             throws RemoteException;
@@ -515,7 +515,7 @@
     int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78;
     int KILL_PIDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79;
     int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80;
-    int GET_TASK_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
+    int GET_TASK_THUMBNAILS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81;
     int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82;
     int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83;
     int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
diff --git a/core/java/android/app/IThumbnailRetriever.aidl b/core/java/android/app/IThumbnailRetriever.aidl
new file mode 100644
index 0000000..2a6737d
--- /dev/null
+++ b/core/java/android/app/IThumbnailRetriever.aidl
@@ -0,0 +1,24 @@
+/* Copyright 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 android.app;
+
+import android.graphics.Bitmap;
+
+/**
+ * System private API for retrieving thumbnails
+ */
+interface IThumbnailRetriever {
+    Bitmap getThumbnail(int index);
+}
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 968d99c..da1c157 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -19,9 +19,9 @@
 -->
 <resources>
     <!-- The width that is used when creating thumbnails of applications. -->
-    <dimen name="thumbnail_width">0dp</dimen>
+    <dimen name="thumbnail_width">64dp</dimen>
     <!-- The height that is used when creating thumbnails of applications. -->
-    <dimen name="thumbnail_height">0dp</dimen>
+    <dimen name="thumbnail_height">100dp</dimen>
     <!-- The standard size (both width and height) of an application icon that
          will be displayed in the app launcher and elsewhere. -->
     <dimen name="app_icon_size">48dip</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
index c5a7df2..a39bef8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java
@@ -465,8 +465,10 @@
                 int id = recentTasks.get(i).id;
                 if (title != null && title.length() > 0 && icon != null) {
                     if (DEBUG) Log.v(TAG, "creating activity desc for id=" + id + ", label=" + title);
+                    ActivityManager.TaskThumbnails thumbs = am.getTaskThumbnails(
+                            recentInfo.persistentId);
                     ActivityDescription item = new ActivityDescription(
-                            am.getTaskThumbnail(recentInfo.persistentId),
+                            thumbs != null ? thumbs.mainThumbnail : null,
                             icon, title, recentInfo.description, intent, id,
                             index, info.packageName);
                     activityDescriptions.add(item);
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2e72068..ef509b1 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -44,6 +44,7 @@
 import android.app.INotificationManager;
 import android.app.IServiceConnection;
 import android.app.IThumbnailReceiver;
+import android.app.IThumbnailRetriever;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -2603,8 +2604,7 @@
                         + " finishing=" + r.finishing);
                     r.makeFinishing();
                     mMainStack.mHistory.remove(i);
-
-                    r.inHistory = false;
+                    r.takeFromHistory();
                     mWindowManager.removeAppToken(r);
                     if (VALIDATE_TOKENS) {
                         mWindowManager.validateAppTokens(mMainStack.mHistory);
@@ -3808,16 +3808,7 @@
                 r = (ActivityRecord)mMainStack.mHistory.get(index);
                 r.icicle = icicle;
                 r.haveState = true;
-                if (thumbnail != null) {
-                    r.thumbnail = thumbnail;
-                    if (r.task != null) {
-                        r.task.lastThumbnail = r.thumbnail;
-                    }
-                }
-                r.description = description;
-                if (r.task != null) {
-                    r.task.lastDescription = r.description;
-                }
+                r.updateThumbnail(thumbnail, description);
                 r.stopped = true;
                 r.state = ActivityState.STOPPED;
                 if (!r.finishing) {
@@ -4851,7 +4842,6 @@
             ActivityRecord next =
                 pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
             ActivityRecord top = null;
-            CharSequence topDescription = null;
             TaskRecord curTask = null;
             int numActivities = 0;
             int numRunning = 0;
@@ -4865,7 +4855,6 @@
                         (top.state == ActivityState.INITIALIZING
                             && top.task == r.task)) {
                     top = r;
-                    topDescription = r.description;
                     curTask = r.task;
                     numActivities = numRunning = 0;
                 }
@@ -4875,9 +4864,6 @@
                 if (r.app != null && r.app.thread != null) {
                     numRunning++;
                 }
-                if (topDescription == null) {
-                    topDescription = r.description;
-                }
 
                 if (localLOGV) Slog.v(
                     TAG, r.intent.getComponent().flattenToShortString()
@@ -4892,13 +4878,15 @@
                     ci.baseActivity = r.intent.getComponent();
                     ci.topActivity = top.intent.getComponent();
                     if (canReadFb) {
-                        if (top.thumbnail != null) {
-                            ci.thumbnail = top.thumbnail;
-                        } else if (top.state == ActivityState.RESUMED) {
+                        if (top.state == ActivityState.RESUMED) {
                             ci.thumbnail = top.stack.screenshotActivities(top);
+                        } else if (top.thumbHolder != null) {
+                            ci.thumbnail = top.thumbHolder.lastThumbnail;
                         }
                     }
-                    ci.description = topDescription;
+                    if (top.thumbHolder != null) {
+                        ci.description = top.thumbHolder.lastDescription;
+                    }
                     ci.numActivities = numActivities;
                     ci.numRunning = numRunning;
                     //System.out.println(
@@ -5015,7 +5003,7 @@
         }
     }
 
-    public Bitmap getTaskThumbnail(int id) {
+    public ActivityManager.TaskThumbnails getTaskThumbnails(int id) {
         synchronized (this) {
             enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
                     "getTaskThumbnail()");
@@ -5024,11 +5012,61 @@
             for (int i=0; i<N; i++) {
                 TaskRecord tr = mRecentTasks.get(i);
                 if (tr.taskId == id) {
-                    if (resumed != null && resumed.task == tr) {
-                        return resumed.stack.screenshotActivities(resumed);
+                    final ActivityManager.TaskThumbnails thumbs
+                            = new ActivityManager.TaskThumbnails();
+                    if (resumed != null && resumed.thumbHolder == tr) {
+                        thumbs.mainThumbnail = resumed.stack.screenshotActivities(resumed);
                     } else {
-                        return tr.lastThumbnail;
+                        thumbs.mainThumbnail = tr.lastThumbnail;
                     }
+                    // How many different sub-thumbnails?
+                    final int NA = mMainStack.mHistory.size();
+                    int j = 0;
+                    ThumbnailHolder holder = null;
+                    while (j < NA) {
+                        ActivityRecord ar = (ActivityRecord)mMainStack.mHistory.get(j);
+                        j++;
+                        if (ar.task == tr) {
+                            holder = ar.thumbHolder;
+                            break;
+                        }
+                    }
+                    ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
+                    thumbs.otherThumbnails = bitmaps;
+                    ActivityRecord lastActivity = null;
+                    while (j < NA) {
+                        ActivityRecord ar = (ActivityRecord)mMainStack.mHistory.get(j);
+                        j++;
+                        if (ar.task != tr) {
+                            break;
+                        }
+                        lastActivity = ar;
+                        if (ar.thumbHolder != holder && holder != null) {
+                            thumbs.numSubThumbbails++;
+                            holder = ar.thumbHolder;
+                            bitmaps.add(holder.lastThumbnail);
+                        }
+                    }
+                    if (lastActivity != null && bitmaps.size() > 0) {
+                        if (resumed == lastActivity) {
+                            Bitmap bm = lastActivity.stack.screenshotActivities(lastActivity);
+                            if (bm != null) {
+                                bitmaps.remove(bitmaps.size()-1);
+                                bitmaps.add(bm);
+                            }
+                        }
+                    }
+                    if (thumbs.numSubThumbbails > 0) {
+                        thumbs.retriever = new IThumbnailRetriever.Stub() {
+                            public Bitmap getThumbnail(int index) {
+                                if (index < 0 || index >= thumbs.otherThumbnails.size()) {
+                                    return null;
+                                }
+                                return thumbs.otherThumbnails.get(index);
+                            }
+                        };
+                    }
+                    return thumbs;
                 }
             }
         }
@@ -5254,9 +5292,9 @@
                 }
                 r = (ActivityRecord)mMainStack.mHistory.get(index);
             }
-            if (thumbnail == null) {
-                thumbnail = r.thumbnail;
-                description = r.description;
+            if (thumbnail == null && r.thumbHolder != null) {
+                thumbnail = r.thumbHolder.lastThumbnail;
+                description = r.thumbHolder.lastDescription;
             }
             if (thumbnail == null && !always) {
                 // If there is no thumbnail, and this entry is not actually
@@ -8440,7 +8478,7 @@
         final String[] args = new String[0];
         for (int i=list.size()-1; i>=0; i--) {
             final ActivityRecord r = (ActivityRecord)list.get(i);
-            final boolean full = !brief && (complete || !r.inHistory);
+            final boolean full = !brief && (complete || !r.isInHistory());
             if (needNL) {
                 pw.println(" ");
                 needNL = false;
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 8d8f303..2703481 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -47,7 +47,7 @@
 /**
  * An entry in the history stack, representing an activity.
  */
-class ActivityRecord extends IApplicationToken.Stub {
+final class ActivityRecord extends IApplicationToken.Stub {
     final ActivityManagerService service; // owner
     final ActivityStack stack; // owner
     final ActivityInfo info; // all about me
@@ -74,6 +74,7 @@
     int realTheme;          // actual theme resource we will use, never 0.
     int windowFlags;        // custom window flags for preview window.
     TaskRecord task;        // the task this is in.
+    ThumbnailHolder thumbHolder; // where our thumbnails should go.
     long launchTime;        // when we starting launching this activity
     long startTime;         // last time this activity was started
     long cpuTimeAtResume;   // the cpu time of host process at the time of resuming activity
@@ -86,9 +87,7 @@
     ArrayList newIntents;   // any pending new intents for single-top mode
     HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold
     UriPermissionOwner uriPermissions; // current special URI access perms.
-    ProcessRecord app;  // if non-null, hosting application
-    Bitmap thumbnail;       // icon representation of paused screen
-    CharSequence description; // textual description of paused screen
+    ProcessRecord app;      // if non-null, hosting application
     ActivityState state;    // current state we are in
     Bundle  icicle;         // last saved activity state
     boolean frontOfTask;    // is this the root activity of its task?
@@ -100,7 +99,6 @@
     boolean configDestroy;  // need to destroy due to config change?
     int configChangeFlags;  // which config values have changed
     boolean keysPaused;     // has key dispatching been paused for it?
-    boolean inHistory;      // are we in the history stack?
     int launchMode;         // the launch mode activity attribute.
     boolean visible;        // does this activity's window need to be shown?
     boolean sleeping;       // have we told the activity to sleep?
@@ -114,6 +112,8 @@
 
     String stringName;      // for caching of toString().
     
+    private boolean inHistory;  // are we in the history stack?
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("packageName="); pw.print(packageName);
                 pw.print(" processName="); pw.println(processName);
@@ -175,6 +175,7 @@
                 pw.print(" launchMode="); pw.println(launchMode);
         pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy);
                 pw.print(" thumbnailNeeded="); pw.println(thumbnailNeeded);
+        pw.print(prefix); pw.print("thumbHolder="); pw.println(thumbHolder);
         if (launchTime != 0 || startTime != 0) {
             pw.print(prefix); pw.print("launchTime=");
                     TimeUtils.formatDuration(launchTime, pw); pw.print(" startTime=");
@@ -328,10 +329,55 @@
         }
     }
 
+    void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) {
+        if (inHistory && !finishing) {
+            if (task != null) {
+                task.numActivities--;
+            }
+            if (newTask != null) {
+                newTask.numActivities++;
+            }
+        }
+        if (newThumbHolder == null) {
+            newThumbHolder = newTask;
+        }
+        task = newTask;
+        if (!isRoot && (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
+            // This is the start of a new sub-task.
+            if (thumbHolder == null) {
+                thumbHolder = new ThumbnailHolder();
+            }
+        } else {
+            thumbHolder = newThumbHolder;
+        }
+    }
+
+    void putInHistory() {
+        if (!inHistory) {
+            inHistory = true;
+            if (task != null && !finishing) {
+                task.numActivities++;
+            }
+        }
+    }
+
+    void takeFromHistory() {
+        if (inHistory) {
+            inHistory = false;
+            if (task != null && !finishing) {
+                task.numActivities--;
+            }
+        }
+    }
+
+    boolean isInHistory() {
+        return inHistory;
+    }
+
     void makeFinishing() {
         if (!finishing) {
             finishing = true;
-            if (task != null) {
+            if (task != null && inHistory) {
                 task.numActivities--;
             }
         }
@@ -430,6 +476,25 @@
         }
     }
 
+    void updateThumbnail(Bitmap newThumbnail, CharSequence description) {
+        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
+            // This is a logical break in the task; it repre
+        }
+        if (thumbHolder != null) {
+            if (newThumbnail != null) {
+                thumbHolder.lastThumbnail = newThumbnail;
+            }
+            thumbHolder.lastDescription = description;
+        }
+    }
+
+    void clearThumbnail() {
+        if (thumbHolder != null) {
+            thumbHolder.lastThumbnail = null;
+            thumbHolder.lastDescription = null;
+        }
+    }
+
     // IApplicationToken
 
     public boolean mayFreezeScreenLocked(ProcessRecord app) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index c087aecf..e1d380b 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -69,7 +69,7 @@
 /**
  * State and management of a single stack of activities.
  */
-public class ActivityStack {
+final public class ActivityStack {
     static final String TAG = ActivityManagerService.TAG;
     static final boolean localLOGV = ActivityManagerService.localLOGV;
     static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH;
@@ -789,10 +789,7 @@
         mLastPausedActivity = prev;
         prev.state = ActivityState.PAUSING;
         prev.task.touchActiveTime();
-        prev.thumbnail = screenshotActivities(prev);
-        if (prev.task != null) {
-            prev.task.lastThumbnail = prev.thumbnail;
-        }
+        prev.updateThumbnail(screenshotActivities(prev), null);
 
         mService.updateCpuStats();
         
@@ -986,7 +983,7 @@
             mService.reportResumedActivityLocked(next);
         }
         
-        next.thumbnail = null;
+        next.clearThumbnail();
         if (mMainStack) {
             mService.setFocusedActivityLocked(next);
         }
@@ -1513,8 +1510,7 @@
                     addPos = i+1;
                     if (!startIt) {
                         mHistory.add(addPos, r);
-                        r.inHistory = true;
-                        r.task.numActivities++;
+                        r.putInHistory();
                         mService.mWindowManager.addAppToken(addPos, r, r.task.taskId,
                                 r.info.screenOrientation, r.fullscreen);
                         if (VALIDATE_TOKENS) {
@@ -1546,9 +1542,8 @@
         
         // Slot the activity into the history stack and proceed
         mHistory.add(addPos, r);
-        r.inHistory = true;
+        r.putInHistory();
         r.frontOfTask = newTask;
-        r.task.numActivities++;
         if (NH > 0) {
             // We want to show the starting preview window if we are
             // switching to a new task, or the next activity's process is
@@ -1711,7 +1706,7 @@
                             // If the activity currently at the bottom has the
                             // same task affinity as the one we are moving,
                             // then merge it into the same task.
-                            target.task = p.task;
+                            target.setTask(p.task, p.thumbHolder, false);
                             if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
                                     + " out to bottom task " + p.task);
                         } else {
@@ -1719,7 +1714,8 @@
                             if (mService.mCurTask <= 0) {
                                 mService.mCurTask = 1;
                             }
-                            target.task = new TaskRecord(mService.mCurTask, target.info, null);
+                            target.setTask(new TaskRecord(mService.mCurTask, target.info, null),
+                                    null, false);
                             target.task.affinityIntent = target.intent;
                             if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
                                     + " out to new task " + target.task);
@@ -1729,6 +1725,7 @@
                             replyChainEnd = targetI;
                         }
                         int dstPos = 0;
+                        ThumbnailHolder curThumbHolder = target.thumbHolder;
                         for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
                             p = (ActivityRecord)mHistory.get(srcPos);
                             if (p.finishing) {
@@ -1736,9 +1733,8 @@
                             }
                             if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
                                     + " out to target's task " + target.task);
-                            task.numActivities--;
-                            p.task = target.task;
-                            target.task.numActivities++;
+                            p.setTask(target.task, curThumbHolder, false);
+                            curThumbHolder = p.thumbHolder;
                             mHistory.remove(srcPos);
                             mHistory.add(dstPos, p);
                             mService.mWindowManager.moveAppToken(dstPos, p);
@@ -1867,12 +1863,10 @@
                             lastReparentPos--;
                         }
                         mHistory.remove(srcPos);
-                        p.task.numActivities--;
-                        p.task = task;
+                        p.setTask(task, null, false);
                         mHistory.add(lastReparentPos, p);
                         if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p
                                 + " in to resetting task " + task);
-                        task.numActivities++;
                         mService.mWindowManager.moveAppToken(lastReparentPos, p);
                         mService.mWindowManager.setAppGroupId(p, p.task.taskId);
                         if (VALIDATE_TOKENS) {
@@ -2525,11 +2519,11 @@
                 if (mService.mCurTask <= 0) {
                     mService.mCurTask = 1;
                 }
-                r.task = new TaskRecord(mService.mCurTask, r.info, intent);
+                r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true);
                 if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                         + " in new task " + r.task);
             } else {
-                r.task = reuseTask;
+                r.setTask(reuseTask, reuseTask, true);
             }
             newTask = true;
             moveHomeToFrontFromLaunchLocked(launchFlags);
@@ -2572,7 +2566,7 @@
             // An existing activity is starting this new activity, so we want
             // to keep the new one in the same task as the one that is starting
             // it.
-            r.task = sourceRecord.task;
+            r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false);
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                     + " in existing task " + r.task);
 
@@ -2583,9 +2577,9 @@
             final int N = mHistory.size();
             ActivityRecord prev =
                 N > 0 ? (ActivityRecord)mHistory.get(N-1) : null;
-            r.task = prev != null
+            r.setTask(prev != null
                     ? prev.task
-                    : new TaskRecord(mService.mCurTask, r.info, intent);
+                    : new TaskRecord(mService.mCurTask, r.info, intent), null, true);
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                     + " in new guessed " + r.task);
         }
@@ -3403,7 +3397,7 @@
         if (r.state != ActivityState.DESTROYED) {
             r.makeFinishing();
             mHistory.remove(r);
-            r.inHistory = false;
+            r.takeFromHistory();
             r.state = ActivityState.DESTROYED;
             mService.mWindowManager.removeAppToken(r);
             if (VALIDATE_TOKENS) {
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index 86cec42..e8c87e1 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -23,7 +23,7 @@
 
 import java.io.PrintWriter;
 
-class TaskRecord {
+class TaskRecord extends ThumbnailHolder {
     final int taskId;       // Unique identifier for this task.
     final String affinity;  // The affinity name for this task, or null.
     Intent intent;          // The original intent that started the task.
@@ -34,8 +34,6 @@
     long lastActiveTime;    // Last time this task was active, including sleep.
     boolean rootWasReset;   // True if the intent at the root of the task had
                             // the FLAG_ACTIVITY_RESET_TASK_IF_NEEDED flag.
-    Bitmap lastThumbnail;   // Last thumbnail captured for this task.
-    CharSequence lastDescription; // Last description captured for this task.
 
     String stringName;      // caching of toString() result.
     
diff --git a/services/java/com/android/server/am/ThumbnailHolder.java b/services/java/com/android/server/am/ThumbnailHolder.java
new file mode 100644
index 0000000..02f4fcb
--- /dev/null
+++ b/services/java/com/android/server/am/ThumbnailHolder.java
@@ -0,0 +1,24 @@
+/*
+ * 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.server.am;
+
+import android.graphics.Bitmap;
+
+public class ThumbnailHolder {
+    Bitmap lastThumbnail;         // Last thumbnail captured for this item.
+    CharSequence lastDescription; // Last description captured for this item.
+}
diff --git a/tests/ActivityTests/Android.mk b/tests/ActivityTests/Android.mk
new file mode 100644
index 0000000..f3c6b5a
--- /dev/null
+++ b/tests/ActivityTests/Android.mk
@@ -0,0 +1,11 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := ActivityTest
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
new file mode 100644
index 0000000..6fa27ed
--- /dev/null
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.test.activity">
+    <uses-permission android:name="android.permission.GET_TASKS" />
+    <uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
+    <application android:label="ActivityTest">
+        <activity android:name="ActivityTestMain">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/ActivityTests/res/values/strings.xml b/tests/ActivityTests/res/values/strings.xml
new file mode 100644
index 0000000..71705cb
--- /dev/null
+++ b/tests/ActivityTests/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 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.
+-->
+
+<resources>
+    <string name="act_title">DpiTest: Unknown Screen</string>
+</resources>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
new file mode 100644
index 0000000..8c5c35a
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -0,0 +1,99 @@
+/*
+ * 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.google.android.test.activity;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.Application;
+import android.os.Bundle;
+import android.graphics.BitmapFactory;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.ScrollView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
+import android.util.DisplayMetrics;
+import android.util.Log;
+
+public class ActivityTestMain extends Activity {
+    private void addThumbnail(LinearLayout container, Bitmap bm) {
+        ImageView iv = new ImageView(this);
+        if (bm != null) {
+            iv.setImageBitmap(bm);
+        }
+        iv.setBackgroundResource(android.R.drawable.gallery_thumb);
+        int w = getResources().getDimensionPixelSize(android.R.dimen.thumbnail_width);
+        int h = getResources().getDimensionPixelSize(android.R.dimen.thumbnail_height);
+        container.addView(iv, new LinearLayout.LayoutParams(w, h));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
+
+        LinearLayout top = new LinearLayout(this);
+        top.setOrientation(LinearLayout.VERTICAL);
+
+        List<ActivityManager.RecentTaskInfo> recents = am.getRecentTasks(10,
+                ActivityManager.RECENT_WITH_EXCLUDED);
+        if (recents != null) {
+            for (int i=0; i<recents.size(); i++) {
+                ActivityManager.RecentTaskInfo r = recents.get(i);
+                ActivityManager.TaskThumbnails tt = r != null
+                        ? am.getTaskThumbnails(r.persistentId) : null;
+                TextView tv = new TextView(this);
+                tv.setText(r.baseIntent.getComponent().flattenToShortString());
+                top.addView(tv, new LinearLayout.LayoutParams(
+                        LinearLayout.LayoutParams.WRAP_CONTENT,
+                        LinearLayout.LayoutParams.WRAP_CONTENT));
+                LinearLayout item = new LinearLayout(this);
+                item.setOrientation(LinearLayout.HORIZONTAL);
+                addThumbnail(item, tt != null ? tt.mainThumbnail : null);
+                for (int j=0; j<tt.numSubThumbbails; j++) {
+                    addThumbnail(item, tt.getSubThumbnail(j));
+                }
+                top.addView(item, new LinearLayout.LayoutParams(
+                        LinearLayout.LayoutParams.WRAP_CONTENT,
+                        LinearLayout.LayoutParams.WRAP_CONTENT));
+            }
+        }
+
+        setContentView(scrollWrap(top));
+    }
+
+    private View scrollWrap(View view) {
+        ScrollView scroller = new ScrollView(this);
+        scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT,
+                ScrollView.LayoutParams.MATCH_PARENT));
+        return scroller;
+    }
+}