Add APIs to remove tasks.

You can remove sub-tasks inside of a task, or an entire task.

When removing an entire task, you can have its process killed
as well.

When the process is killed, any running services will get an
onTaskRemoved() callback for them to do cleanup before their
process is killed (and the service possibly restarted).

Or they can set a new android:stopWithTask attribute to just
have the service automatically (cleanly) stopped at this point.

Change-Id: I1891bc2da006fa53b99c52f9040f1145650e6808
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 9990b7b..768d990 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -441,8 +441,7 @@
     /**
      * List of intents that were used to start the most recent tasks.
      */
-    final ArrayList<TaskRecord> mRecentTasks
-            = new ArrayList<TaskRecord>();
+    final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
 
     /**
      * All of the applications we currently have running organized by name.
@@ -450,8 +449,7 @@
      * returned by the package manager), and the keys are ApplicationRecord
      * objects.
      */
-    final ProcessMap<ProcessRecord> mProcessNames
-            = new ProcessMap<ProcessRecord>();
+    final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
 
     /**
      * The currently running heavy-weight process, if any.
@@ -480,8 +478,7 @@
      * <p>NOTE: This object is protected by its own lock, NOT the global
      * activity manager lock!
      */
-    final SparseArray<ProcessRecord> mPidsSelfLocked
-            = new SparseArray<ProcessRecord>();
+    final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
 
     /**
      * All of the processes that have been forced to be foreground.  The key
@@ -2981,7 +2978,10 @@
         
         synchronized (this) {
             if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
-                Process.killProcess(app.pid);
+                Slog.w(TAG, "Killing " + app + ": background ANR");
+                EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                        app.processName, app.setAdj, "background ANR");
+                Process.killProcessQuiet(app.pid);
                 return;
             }
     
@@ -3446,7 +3446,9 @@
                     bringDownServiceLocked(sr, true);
                 }
             }
-            Process.killProcess(pid);
+            EventLog.writeEvent(EventLogTags.AM_KILL, pid,
+                    app.processName, app.setAdj, "start timeout");
+            Process.killProcessQuiet(pid);
             if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
                 Slog.w(TAG, "Unattached app died before backup, skipping");
                 try {
@@ -3492,7 +3494,7 @@
                     + " (IApplicationThread " + thread + "); dropping process");
             EventLog.writeEvent(EventLogTags.AM_DROP_PROCESS, pid);
             if (pid > 0 && pid != MY_PID) {
-                Process.killProcess(pid);
+                Process.killProcessQuiet(pid);
             } else {
                 try {
                     thread.scheduleExit();
@@ -4832,11 +4834,6 @@
                 throw new SecurityException(msg);
             }
 
-            final boolean canReadFb = (flags&ActivityManager.TASKS_GET_THUMBNAILS) != 0
-                    && checkCallingPermission(
-                            android.Manifest.permission.READ_FRAME_BUFFER)
-                            == PackageManager.PERMISSION_GRANTED;
-
             int pos = mMainStack.mHistory.size()-1;
             ActivityRecord next =
                 pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null;
@@ -4876,13 +4873,6 @@
                     ci.id = curTask.taskId;
                     ci.baseActivity = r.intent.getComponent();
                     ci.topActivity = top.intent.getComponent();
-                    if (canReadFb) {
-                        if (top.state == ActivityState.RESUMED) {
-                            ci.thumbnail = top.stack.screenshotActivities(top);
-                        } else if (top.thumbHolder != null) {
-                            ci.thumbnail = top.thumbHolder.lastThumbnail;
-                        }
-                    }
                     if (top.thumbHolder != null) {
                         ci.description = top.thumbHolder.lastDescription;
                     }
@@ -4955,8 +4945,6 @@
 
             IPackageManager pm = AppGlobals.getPackageManager();
             
-            ActivityRecord resumed = mMainStack.mResumedActivity;
-            
             final int N = mRecentTasks.size();
             ArrayList<ActivityManager.RecentTaskInfo> res
                     = new ArrayList<ActivityManager.RecentTaskInfo>(
@@ -5002,75 +4990,122 @@
         }
     }
 
-    public ActivityManager.TaskThumbnails getTaskThumbnails(int id) {
-        synchronized (this) {
-            enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
-                    "getTaskThumbnail()");
-            ActivityRecord resumed = mMainStack.mResumedActivity;
-            final int N = mRecentTasks.size();
-            for (int i=0; i<N; i++) {
-                TaskRecord tr = mRecentTasks.get(i);
-                if (tr.taskId == id) {
-                    final ActivityManager.TaskThumbnails thumbs
-                            = new ActivityManager.TaskThumbnails();
-                    if (resumed != null && resumed.thumbHolder == tr) {
-                        thumbs.mainThumbnail = resumed.stack.screenshotActivities(resumed);
-                    } else {
-                        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;
-                }
+    private TaskRecord taskForIdLocked(int id) {
+        final int N = mRecentTasks.size();
+        for (int i=0; i<N; i++) {
+            TaskRecord tr = mRecentTasks.get(i);
+            if (tr.taskId == id) {
+                return tr;
             }
         }
         return null;
     }
+
+    public ActivityManager.TaskThumbnails getTaskThumbnails(int id) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER,
+                    "getTaskThumbnails()");
+            TaskRecord tr = taskForIdLocked(id);
+            if (tr != null) {
+                return mMainStack.getTaskThumbnailsLocked(tr);
+            }
+        }
+        return null;
+    }
+
+    public boolean removeSubTask(int taskId, int subTaskIndex) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
+                    "removeSubTask()");
+            long ident = Binder.clearCallingIdentity();
+            try {
+                return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex) != null;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    private void removeTaskProcessesLocked(ActivityRecord root) {
+        TaskRecord tr = root.task;
+        Intent baseIntent = new Intent(
+                tr.intent != null ? tr.intent : tr.affinityIntent);
+        ComponentName component = baseIntent.getComponent();
+        if (component == null) {
+            Slog.w(TAG, "Now component for base intent of task: " + tr);
+            return;
+        }
+
+        // Find any running services associated with this app.
+        ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+        for (ServiceRecord sr : mServices.values()) {
+            if (sr.packageName.equals(component.getPackageName())) {
+                services.add(sr);
+            }
+        }
+
+        // Take care of any running services associated with the app.
+        for (int i=0; i<services.size(); i++) {
+            ServiceRecord sr = services.get(i);
+            if (sr.startRequested) {
+                if ((sr.serviceInfo.flags&ServiceInfo.FLAG_STOP_WITH_TASK) != 0) {
+                    stopServiceLocked(sr);
+                } else {
+                    sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
+                            sr.makeNextStartId(), baseIntent, -1));
+                    if (sr.app != null && sr.app.thread != null) {
+                        sendServiceArgsLocked(sr, false);
+                    }
+                }
+            }
+        }
+
+        // Find any running processes associated with this app.
+        ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
+        SparseArray<ProcessRecord> appProcs
+                = mProcessNames.getMap().get(component.getPackageName());
+        if (appProcs != null) {
+            for (int i=0; i<appProcs.size(); i++) {
+                procs.add(appProcs.valueAt(i));
+            }
+        }
+
+        // Kill the running processes.
+        for (int i=0; i<procs.size(); i++) {
+            ProcessRecord pr = procs.get(i);
+            if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+                Slog.i(TAG, "Killing " + pr + ": remove task");
+                EventLog.writeEvent(EventLogTags.AM_KILL, pr.pid,
+                        pr.processName, pr.setAdj, "remove task");
+                Process.killProcessQuiet(pr.pid);
+            } else {
+                pr.waitingToKill = "remove task";
+            }
+        }
+    }
+
+    public boolean removeTask(int taskId, int flags) {
+        synchronized (this) {
+            enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS,
+                    "removeTask()");
+            long ident = Binder.clearCallingIdentity();
+            try {
+                ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1);
+                if (r != null) {
+                    mRecentTasks.remove(r.task);
+
+                    if ((flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0) {
+                        removeTaskProcessesLocked(r);
+                    }
+
+                    return true;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+        return false;
+    }
     
     private final int findAffinityTaskTopLocked(int startIndex, String affinity) {
         int j;
@@ -5123,21 +5158,18 @@
             }
             final long origId = Binder.clearCallingIdentity();
             try {
-                int N = mRecentTasks.size();
-                for (int i=0; i<N; i++) {
-                    TaskRecord tr = mRecentTasks.get(i);
-                    if (tr.taskId == task) {
-                        if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
-                            mMainStack.mUserLeaving = true;
-                        }
-                        if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
-                            // Caller wants the home activity moved with it.  To accomplish this,
-                            // we'll just move the home task to the top first.
-                            mMainStack.moveHomeToFrontLocked();
-                        }
-                        mMainStack.moveTaskToFrontLocked(tr, null);
-                        return;
+                TaskRecord tr = taskForIdLocked(task);
+                if (tr != null) {
+                    if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) {
+                        mMainStack.mUserLeaving = true;
                     }
+                    if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) {
+                        // Caller wants the home activity moved with it.  To accomplish this,
+                        // we'll just move the home task to the top first.
+                        mMainStack.moveHomeToFrontLocked();
+                    }
+                    mMainStack.moveTaskToFrontLocked(tr, null);
+                    return;
                 }
                 for (int i=mMainStack.mHistory.size()-1; i>=0; i--) {
                     ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i);
@@ -6661,11 +6693,10 @@
             }
             if (app.pid > 0 && app.pid != MY_PID) {
                 handleAppCrashLocked(app);
-                Slog.i(ActivityManagerService.TAG, "Killing "
-                        + app.processName + " (pid=" + app.pid + "): user's request");
+                Slog.i(ActivityManagerService.TAG, "Killing " + app + ": user's request");
                 EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
                         app.processName, app.setAdj, "user's request after error");
-                Process.killProcess(app.pid);
+                Process.killProcessQuiet(app.pid);
             }
         }
     }
@@ -8946,7 +8977,7 @@
                         + " in dying process " + proc.processName);
                 EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
                         capp.processName, capp.setAdj, "dying provider " + proc.processName);
-                Process.killProcess(capp.pid);
+                Process.killProcessQuiet(capp.pid);
             }
         }
         
@@ -9453,7 +9484,7 @@
                 if (si.doneExecutingCount > 0) {
                     flags |= Service.START_FLAG_REDELIVERY;
                 }
-                r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent);
+                r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
             } catch (RemoteException e) {
                 // Remote process gone...  we'll let the normal cleanup take
                 // care of this.
@@ -9539,11 +9570,8 @@
         // pending arguments, then fake up one so its onStartCommand() will
         // be called.
         if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
-            r.lastStartId++;
-            if (r.lastStartId < 1) {
-                r.lastStartId = 1;
-            }
-            r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId, null, -1));
+            r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
+                    null, -1));
         }
         
         sendServiceArgsLocked(r, true);
@@ -9897,11 +9925,7 @@
             }
             r.startRequested = true;
             r.callStart = false;
-            r.lastStartId++;
-            if (r.lastStartId < 1) {
-                r.lastStartId = 1;
-            }
-            r.pendingStarts.add(new ServiceRecord.StartItem(r, r.lastStartId,
+            r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
                     service, targetPermissionUid));
             r.lastActivity = SystemClock.uptimeMillis();
             synchronized (r.stats.getBatteryStats()) {
@@ -9943,6 +9967,15 @@
         }
     }
 
+    private void stopServiceLocked(ServiceRecord service) {
+        synchronized (service.stats.getBatteryStats()) {
+            service.stats.stopRunningLocked();
+        }
+        service.startRequested = false;
+        service.callStart = false;
+        bringDownServiceLocked(service, false);
+    }
+
     public int stopService(IApplicationThread caller, Intent service,
             String resolvedType) {
         // Refuse possible leaked file descriptors
@@ -9966,14 +9999,12 @@
             ServiceLookupResult r = findServiceLocked(service, resolvedType);
             if (r != null) {
                 if (r.record != null) {
-                    synchronized (r.record.stats.getBatteryStats()) {
-                        r.record.stats.stopRunningLocked();
-                    }
-                    r.record.startRequested = false;
-                    r.record.callStart = false;
                     final long origId = Binder.clearCallingIdentity();
-                    bringDownServiceLocked(r.record, false);
-                    Binder.restoreCallingIdentity(origId);
+                    try {
+                        stopServiceLocked(r.record);
+                    } finally {
+                        Binder.restoreCallingIdentity(origId);
+                    }
                     return 1;
                 }
                 return -1;
@@ -10035,7 +10066,7 @@
                         }
                     }
                     
-                    if (r.lastStartId != startId) {
+                    if (r.getLastStartId() != startId) {
                         return false;
                     }
                     
@@ -10476,7 +10507,7 @@
                         case Service.START_NOT_STICKY: {
                             // We are done with the associated start arguments.
                             r.findDeliveredStart(startId, true);
-                            if (r.lastStartId == startId) {
+                            if (r.getLastStartId() == startId) {
                                 // There is no more work, and this service
                                 // doesn't want to hang around if killed.
                                 r.stopIfKilled = true;
@@ -10496,6 +10527,12 @@
                             }
                             break;
                         }
+                        case Service.START_TASK_REMOVED_COMPLETE: {
+                            // Special processing for onTaskRemoved().  Don't
+                            // impact normal onStartCommand() processing.
+                            r.findDeliveredStart(startId, true);
+                            break;
+                        }
                         default:
                             throw new IllegalArgumentException(
                                     "Unknown service start result: " + res);
@@ -12885,23 +12922,31 @@
                 if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG,
                         "Setting process group of " + app.processName
                         + " to " + app.curSchedGroup);
-                if (true) {
-                    long oldId = Binder.clearCallingIdentity();
-                    try {
-                        Process.setProcessGroup(app.pid, app.curSchedGroup);
-                    } catch (Exception e) {
-                        Slog.w(TAG, "Failed setting process group of " + app.pid
-                                + " to " + app.curSchedGroup);
-                        e.printStackTrace();
-                    } finally {
-                        Binder.restoreCallingIdentity(oldId);
-                    }
-                }
-                if (false) {
-                    if (app.thread != null) {
+                if (app.waitingToKill != null &&
+                        app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
+                    Slog.i(TAG, "Killing " + app + ": " + app.waitingToKill);
+                    EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                            app.processName, app.setAdj, app.waitingToKill);
+                    Process.killProcessQuiet(app.pid);
+                } else {
+                    if (true) {
+                        long oldId = Binder.clearCallingIdentity();
                         try {
-                            app.thread.setSchedulingGroup(app.curSchedGroup);
-                        } catch (RemoteException e) {
+                            Process.setProcessGroup(app.pid, app.curSchedGroup);
+                        } catch (Exception e) {
+                            Slog.w(TAG, "Failed setting process group of " + app.pid
+                                    + " to " + app.curSchedGroup);
+                            e.printStackTrace();
+                        } finally {
+                            Binder.restoreCallingIdentity(oldId);
+                        }
+                    }
+                    if (false) {
+                        if (app.thread != null) {
+                            try {
+                                app.thread.setSchedulingGroup(app.curSchedGroup);
+                            } catch (RemoteException e) {
+                            }
                         }
                     }
                 }
@@ -13024,7 +13069,9 @@
                         + (app.thread != null ? app.thread.asBinder() : null)
                         + ")\n");
                     if (app.pid > 0 && app.pid != MY_PID) {
-                        Process.killProcess(app.pid);
+                        EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                                app.processName, app.setAdj, "empty");
+                        Process.killProcessQuiet(app.pid);
                     } else {
                         try {
                             app.thread.scheduleExit();
@@ -13090,7 +13137,9 @@
                             + (app.thread != null ? app.thread.asBinder() : null)
                             + ")\n");
                         if (app.pid > 0 && app.pid != MY_PID) {
-                            Process.killProcess(app.pid);
+                            EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                                    app.processName, app.setAdj, "empty");
+                            Process.killProcessQuiet(app.pid);
                         } else {
                             try {
                                 app.thread.scheduleExit();
@@ -13147,7 +13196,9 @@
                               + (app.thread != null ? app.thread.asBinder() : null)
                               + ")\n");
                         if (app.pid > 0 && app.pid != MY_PID) {
-                            Process.killProcess(app.pid);
+                            EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                                    app.processName, app.setAdj, "old background");
+                            Process.killProcessQuiet(app.pid);
                         } else {
                             try {
                                 app.thread.scheduleExit();
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index e1d380b..9558895 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -21,8 +21,10 @@
 import com.android.server.am.ActivityManagerService.PendingActivityLaunch;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
+import android.app.IThumbnailRetriever;
 import static android.app.IActivityManager.START_CLASS_NOT_FOUND;
 import static android.app.IActivityManager.START_DELIVERED_TO_TOP;
 import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
@@ -69,7 +71,7 @@
 /**
  * State and management of a single stack of activities.
  */
-final public class ActivityStack {
+final class ActivityStack {
     static final String TAG = ActivityManagerService.TAG;
     static final boolean localLOGV = ActivityManagerService.localLOGV;
     static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH;
@@ -135,14 +137,14 @@
      * The back history of all previous (and possibly still
      * running) activities.  It contains HistoryRecord objects.
      */
-    final ArrayList mHistory = new ArrayList();
+    final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>();
     
     /**
      * List of running activities, sorted by recent usage.
      * The first entry in the list is the least recently used.
      * It contains HistoryRecord objects.
      */
-    final ArrayList mLRUActivities = new ArrayList();
+    final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>();
 
     /**
      * List of activities that are waiting for a new activity
@@ -346,7 +348,7 @@
     final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
         int i = mHistory.size()-1;
         while (i >= 0) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (!r.finishing && r != notTop) {
                 return r;
             }
@@ -358,7 +360,7 @@
     final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
         int i = mHistory.size()-1;
         while (i >= 0) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (!r.finishing && !r.delayedResume && r != notTop) {
                 return r;
             }
@@ -379,7 +381,7 @@
     final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) {
         int i = mHistory.size()-1;
         while (i >= 0) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             // Note: the taskId check depends on real taskId fields being non-zero
             if (!r.finishing && (token != r) && (taskId != r.task.taskId)) {
                 return r;
@@ -425,7 +427,7 @@
 
         final int N = mHistory.size();
         for (int i=(N-1); i>=0; i--) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (!r.finishing && r.task != cp
                     && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
                 cp = r.task;
@@ -469,7 +471,7 @@
 
         final int N = mHistory.size();
         for (int i=(N-1); i>=0; i--) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (!r.finishing) {
                 if (r.intent.getComponent().equals(cls)) {
                     //Slog.i(TAG, "Found matching class!");
@@ -504,6 +506,7 @@
         }
 
         r.app = app;
+        app.waitingToKill = null;
 
         if (localLOGV) Slog.v(TAG, "Launching: " + r);
 
@@ -678,7 +681,7 @@
         }
         // Ensure activities are no longer sleeping.
         for (int i=mHistory.size()-1; i>=0; i--) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             r.setSleeping(false);
         }
         mGoingToSleepActivities.clear();
@@ -724,7 +727,7 @@
             // Make sure any stopped but visible activities are now sleeping.
             // This ensures that the activity's onStop() is called.
             for (int i=mHistory.size()-1; i>=0; i--) {
-                ActivityRecord r = (ActivityRecord)mHistory.get(i);
+                ActivityRecord r = mHistory.get(i);
                 if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) {
                     r.setSleeping(true);
                 }
@@ -862,7 +865,7 @@
         synchronized (mService) {
             int index = indexOfTokenLocked(token);
             if (index >= 0) {
-                r = (ActivityRecord)mHistory.get(index);
+                r = mHistory.get(index);
                 mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
                 if (mPausingActivity == r) {
                     r.state = ActivityState.PAUSED;
@@ -1024,7 +1027,7 @@
         ActivityRecord r;
         boolean behindFullscreen = false;
         for (; i>=0; i--) {
-            r = (ActivityRecord)mHistory.get(i);
+            r = mHistory.get(i);
             if (DEBUG_VISBILITY) Slog.v(
                     TAG, "Make visible? " + r + " finishing=" + r.finishing
                     + " state=" + r.state);
@@ -1108,7 +1111,7 @@
         // Now for any activities that aren't visible to the user, make
         // sure they no longer are keeping the screen frozen.
         while (i >= 0) {
-            r = (ActivityRecord)mHistory.get(i);
+            r = mHistory.get(i);
             if (DEBUG_VISBILITY) Slog.v(
                     TAG, "Make invisible? " + r + " finishing=" + r.finishing
                     + " state=" + r.state
@@ -1499,7 +1502,7 @@
             // If starting in an existing task, find where that is...
             boolean startIt = true;
             for (int i = NH-1; i >= 0; i--) {
-                ActivityRecord p = (ActivityRecord)mHistory.get(i);
+                ActivityRecord p = mHistory.get(i);
                 if (p.finishing) {
                     continue;
                 }
@@ -1646,7 +1649,7 @@
         int replyChainEnd = -1;
         int lastReparentPos = -1;
         for (int i=mHistory.size()-1; i>=-1; i--) {
-            ActivityRecord below = i >= 0 ? (ActivityRecord)mHistory.get(i) : null;
+            ActivityRecord below = i >= 0 ? mHistory.get(i) : null;
             
             if (below != null && below.finishing) {
                 continue;
@@ -1700,7 +1703,7 @@
                         // bottom of the activity stack.  This also keeps it
                         // correctly ordered with any activities we previously
                         // moved.
-                        ActivityRecord p = (ActivityRecord)mHistory.get(0);
+                        ActivityRecord p = mHistory.get(0);
                         if (target.taskAffinity != null
                                 && target.taskAffinity.equals(p.task.affinity)) {
                             // If the activity currently at the bottom has the
@@ -1727,7 +1730,7 @@
                         int dstPos = 0;
                         ThumbnailHolder curThumbHolder = target.thumbHolder;
                         for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
-                            p = (ActivityRecord)mHistory.get(srcPos);
+                            p = mHistory.get(srcPos);
                             if (p.finishing) {
                                 continue;
                             }
@@ -1764,7 +1767,7 @@
                             // like these are all in the reply chain.
                             replyChainEnd = targetI+1;
                             while (replyChainEnd < mHistory.size() &&
-                                    ((ActivityRecord)mHistory.get(
+                                    (mHistory.get(
                                                 replyChainEnd)).task == task) {
                                 replyChainEnd++;
                             }
@@ -1774,7 +1777,7 @@
                         }
                         ActivityRecord p = null;
                         for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
-                            p = (ActivityRecord)mHistory.get(srcPos);
+                            p = mHistory.get(srcPos);
                             if (p.finishing) {
                                 continue;
                             }
@@ -1834,7 +1837,7 @@
                     }
                     ActivityRecord p = null;
                     for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) {
-                        p = (ActivityRecord)mHistory.get(srcPos);
+                        p = mHistory.get(srcPos);
                         if (p.finishing) {
                             continue;
                         }
@@ -1852,7 +1855,7 @@
                         replyChainEnd = targetI;
                     }
                     for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) {
-                        ActivityRecord p = (ActivityRecord)mHistory.get(srcPos);
+                        ActivityRecord p = mHistory.get(srcPos);
                         if (p.finishing) {
                             continue;
                         }
@@ -1881,7 +1884,7 @@
                     // below so it remains singleTop.
                     if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) {
                         for (int j=lastReparentPos-1; j>=0; j--) {
-                            ActivityRecord p = (ActivityRecord)mHistory.get(j);
+                            ActivityRecord p = mHistory.get(j);
                             if (p.finishing) {
                                 continue;
                             }
@@ -1922,7 +1925,7 @@
         // First find the requested task.
         while (i > 0) {
             i--;
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (r.task.taskId == taskId) {
                 i++;
                 break;
@@ -1932,7 +1935,7 @@
         // Now clear it.
         while (i > 0) {
             i--;
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (r.finishing) {
                 continue;
             }
@@ -1944,7 +1947,7 @@
                 ActivityRecord ret = r;
                 while (i < (mHistory.size()-1)) {
                     i++;
-                    r = (ActivityRecord)mHistory.get(i);
+                    r = mHistory.get(i);
                     if (r.task.taskId != taskId) {
                         break;
                     }
@@ -1980,6 +1983,28 @@
     }
 
     /**
+     * Completely remove all activities associated with an existing
+     * task starting at a specified index.
+     */
+    private final void performClearTaskAtIndexLocked(int taskId, int i) {
+        while (i < (mHistory.size()-1)) {
+            ActivityRecord r = mHistory.get(i);
+            if (r.task.taskId != taskId) {
+                // Whoops hit the end.
+                return;
+            }
+            if (r.finishing) {
+                i++;
+                continue;
+            }
+            if (!finishActivityLocked(r, i, Activity.RESULT_CANCELED,
+                    null, "clear")) {
+                i++;
+            }
+        }
+    }
+
+    /**
      * Completely remove all activities associated with an existing task.
      */
     private final void performClearTaskLocked(int taskId) {
@@ -1988,37 +2013,23 @@
         // First find the requested task.
         while (i > 0) {
             i--;
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (r.task.taskId == taskId) {
                 i++;
                 break;
             }
         }
 
-        // Now clear it.
+        // Now find the start and clear it.
         while (i > 0) {
             i--;
-            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            ActivityRecord r = mHistory.get(i);
             if (r.finishing) {
                 continue;
             }
             if (r.task.taskId != taskId) {
                 // We hit the bottom.  Now finish it all...
-                while (i < (mHistory.size()-1)) {
-                    i++;
-                    r = (ActivityRecord)mHistory.get(i);
-                    if (r.task.taskId != taskId) {
-                        // Whoops hit the end.
-                        return;
-                    }
-                    if (r.finishing) {
-                        continue;
-                    }
-                    if (finishActivityLocked(r, i, Activity.RESULT_CANCELED,
-                            null, "clear")) {
-                        i--;
-                    }
-                }
+                performClearTaskAtIndexLocked(taskId, i+1);
                 return;
             }
         }
@@ -2032,7 +2043,7 @@
         int i = mHistory.size();
         while (i > 0) {
             i--;
-            ActivityRecord candidate = (ActivityRecord)mHistory.get(i);
+            ActivityRecord candidate = mHistory.get(i);
             if (candidate.task.taskId != task) {
                 break;
             }
@@ -2049,9 +2060,9 @@
      * brought to the front.
      */
     private final ActivityRecord moveActivityToFrontLocked(int where) {
-        ActivityRecord newTop = (ActivityRecord)mHistory.remove(where);
+        ActivityRecord newTop = mHistory.remove(where);
         int top = mHistory.size();
-        ActivityRecord oldTop = (ActivityRecord)mHistory.get(top-1);
+        ActivityRecord oldTop = mHistory.get(top-1);
         mHistory.add(top, newTop);
         oldTop.frontOfTask = false;
         newTop.frontOfTask = true;
@@ -2094,7 +2105,7 @@
             if (DEBUG_RESULTS) Slog.v(
                 TAG, "Sending result to " + resultTo + " (index " + index + ")");
             if (index >= 0) {
-                sourceRecord = (ActivityRecord)mHistory.get(index);
+                sourceRecord = mHistory.get(index);
                 if (requestCode >= 0 && !sourceRecord.finishing) {
                     resultRecord = sourceRecord;
                 }
@@ -2576,7 +2587,7 @@
             // this case should never happen.
             final int N = mHistory.size();
             ActivityRecord prev =
-                N > 0 ? (ActivityRecord)mHistory.get(N-1) : null;
+                N > 0 ? mHistory.get(N-1) : null;
             r.setTask(prev != null
                     ? prev.task
                     : new TaskRecord(mService.mCurTask, r.info, intent), null, true);
@@ -3021,7 +3032,7 @@
             // Get the activity record.
             int index = indexOfTokenLocked(token);
             if (index >= 0) {
-                ActivityRecord r = (ActivityRecord)mHistory.get(index);
+                ActivityRecord r = mHistory.get(index);
 
                 if (fromTimeout) {
                     reportActivityLaunchedLocked(fromTimeout, r, -1, -1);
@@ -3153,12 +3164,12 @@
         if (index < 0) {
             return false;
         }
-        ActivityRecord r = (ActivityRecord)mHistory.get(index);
+        ActivityRecord r = mHistory.get(index);
 
         // Is this the last activity left?
         boolean lastActivity = true;
         for (int i=mHistory.size()-1; i>=0; i--) {
-            ActivityRecord p = (ActivityRecord)mHistory.get(i);
+            ActivityRecord p = mHistory.get(i);
             if (!p.finishing && p != r) {
                 lastActivity = false;
                 break;
@@ -3193,7 +3204,7 @@
                 System.identityHashCode(r),
                 r.task.taskId, r.shortComponentName, reason);
         if (index < (mHistory.size()-1)) {
-            ActivityRecord next = (ActivityRecord)mHistory.get(index+1);
+            ActivityRecord next = mHistory.get(index+1);
             if (next.task == r.task) {
                 if (r.frontOfTask) {
                     // The next activity is now the front of the task.
@@ -3249,7 +3260,7 @@
 
         if (mResumedActivity == r) {
             boolean endTask = index <= 0
-                    || ((ActivityRecord)mHistory.get(index-1)).task != r.task;
+                    || (mHistory.get(index-1)).task != r.task;
             if (DEBUG_TRANSITION) Slog.v(TAG,
                     "Prepare close transition: finishing " + r);
             mService.mWindowManager.prepareAppTransition(endTask
@@ -3391,6 +3402,7 @@
         // Get rid of any pending idle timeouts.
         mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
         mHandler.removeMessages(IDLE_TIMEOUT_MSG, r);
+        mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r);
     }
 
     private final void removeActivityFromHistoryLocked(ActivityRecord r) {
@@ -3516,7 +3528,7 @@
             
             int index = indexOfTokenLocked(token);
             if (index >= 0) {
-                ActivityRecord r = (ActivityRecord)mHistory.get(index);
+                ActivityRecord r = mHistory.get(index);
                 if (r.state == ActivityState.DESTROYING) {
                     final long origId = Binder.clearCallingIdentity();
                     removeActivityFromHistoryLocked(r);
@@ -3558,7 +3570,7 @@
     final void moveHomeToFrontLocked() {
         TaskRecord homeTask = null;
         for (int i=mHistory.size()-1; i>=0; i--) {
-            ActivityRecord hr = (ActivityRecord)mHistory.get(i);
+            ActivityRecord hr = mHistory.get(i);
             if (hr.isHomeActivity) {
                 homeTask = hr.task;
                 break;
@@ -3576,7 +3588,7 @@
         final int task = tr.taskId;
         int top = mHistory.size()-1;
 
-        if (top < 0 || ((ActivityRecord)mHistory.get(top)).task.taskId == task) {
+        if (top < 0 || (mHistory.get(top)).task.taskId == task) {
             // nothing to do!
             return;
         }
@@ -3591,7 +3603,7 @@
         // Shift all activities with this task up to the top
         // of the stack, keeping them in the same internal order.
         while (pos >= 0) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(pos);
+            ActivityRecord r = mHistory.get(pos);
             if (localLOGV) Slog.v(
                 TAG, "At " + pos + " ckp " + r.task + ": " + r);
             if (r.task.taskId == task) {
@@ -3680,7 +3692,7 @@
         // Shift all activities with this task down to the bottom
         // of the stack, keeping them in the same internal order.
         while (pos < N) {
-            ActivityRecord r = (ActivityRecord)mHistory.get(pos);
+            ActivityRecord r = mHistory.get(pos);
             if (localLOGV) Slog.v(
                 TAG, "At " + pos + " ckp " + r.task + ": " + r);
             if (r.task.taskId == task) {
@@ -3714,6 +3726,106 @@
         return true;
     }
     
+    public ActivityManager.TaskThumbnails getTaskThumbnailsLocked(TaskRecord tr) {
+        TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true);
+        ActivityRecord resumed = mResumedActivity;
+        if (resumed != null && resumed.thumbHolder == tr) {
+            info.mainThumbnail = resumed.stack.screenshotActivities(resumed);
+        } else {
+            info.mainThumbnail = tr.lastThumbnail;
+        }
+        return info;
+    }
+
+    public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex) {
+        TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false);
+        if (info.root == null) {
+            Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId);
+            return null;
+        }
+
+        if (subTaskIndex < 0) {
+            // Just remove the entire task.
+            performClearTaskAtIndexLocked(taskId, info.rootIndex);
+            return info.root;
+        }
+
+        if (subTaskIndex >= info.subtasks.size()) {
+            Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex);
+            return null;
+        }
+
+        // Remove all of this task's activies starting at the sub task.
+        TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex);
+        performClearTaskAtIndexLocked(taskId, subtask.index);
+        return subtask.activity;
+    }
+
+    public TaskAccessInfo getTaskAccessInfoLocked(int taskId, boolean inclThumbs) {
+        ActivityRecord resumed = mResumedActivity;
+        final TaskAccessInfo thumbs = new TaskAccessInfo();
+        // How many different sub-thumbnails?
+        final int NA = mHistory.size();
+        int j = 0;
+        ThumbnailHolder holder = null;
+        while (j < NA) {
+            ActivityRecord ar = mHistory.get(j);
+            if (!ar.finishing && ar.task.taskId == taskId) {
+                holder = ar.thumbHolder;
+                break;
+            }
+            j++;
+        }
+
+        if (j >= NA) {
+            return thumbs;
+        }
+
+        thumbs.root = mHistory.get(j);
+        thumbs.rootIndex = j;
+
+        ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>();
+        thumbs.subtasks = subtasks;
+        ActivityRecord lastActivity = null;
+        while (j < NA) {
+            ActivityRecord ar = mHistory.get(j);
+            j++;
+            if (ar.finishing) {
+                continue;
+            }
+            if (ar.task.taskId != taskId) {
+                break;
+            }
+            lastActivity = ar;
+            if (ar.thumbHolder != holder && holder != null) {
+                thumbs.numSubThumbbails++;
+                holder = ar.thumbHolder;
+                TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask();
+                sub.thumbnail = holder.lastThumbnail;
+                sub.activity = ar;
+                sub.index = j-1;
+                subtasks.add(sub);
+            }
+        }
+        if (lastActivity != null && subtasks.size() > 0) {
+            if (resumed == lastActivity) {
+                TaskAccessInfo.SubTask sub = subtasks.get(subtasks.size()-1);
+                sub.thumbnail = lastActivity.stack.screenshotActivities(lastActivity);
+            }
+        }
+        if (thumbs.numSubThumbbails > 0) {
+            thumbs.retriever = new IThumbnailRetriever.Stub() {
+                public Bitmap getThumbnail(int index) {
+                    if (index < 0 || index >= thumbs.subtasks.size()) {
+                        return null;
+                    }
+                    return thumbs.subtasks.get(index).thumbnail;
+                }
+            };
+        }
+        return thumbs;
+    }
+
     private final void logStartActivity(int tag, ActivityRecord r,
             TaskRecord task) {
         EventLog.writeEvent(tag,
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 353ff6d..e39c239 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -65,6 +65,7 @@
     boolean foregroundServices; // Running any services that are foreground?
     boolean bad;                // True if disabled in the bad process list
     boolean killedBackground;   // True when proc has been killed due to too many bg
+    String waitingToKill;       // Process is waiting to be killed when in the bg; reason
     IBinder forcingToForeground;// Token that is forcing this process to be foreground
     int adjSeq;                 // Sequence id for identifying oom_adj assignment cycles
     int lruSeq;                 // Sequence id for identifying LRU update cycles
@@ -202,8 +203,9 @@
                 pw.print(" lastLowMemory=");
                 TimeUtils.formatDuration(lastLowMemory, now, pw);
                 pw.print(" reportLowMemory="); pw.println(reportLowMemory);
-        if (killedBackground) {
-            pw.print(prefix); pw.print("killedBackground="); pw.println(killedBackground);
+        if (killedBackground || waitingToKill != null) {
+            pw.print(prefix); pw.print("killedBackground="); pw.print(killedBackground);
+                    pw.print(" waitingToKill="); pw.println(waitingToKill);
         }
         if (debugging || crashing || crashDialog != null || notResponding
                 || anrDialog != null || bad) {
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 1a617dd..da00c0c 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -84,7 +84,6 @@
     boolean startRequested; // someone explicitly called start?
     boolean stopIfKilled;   // last onStart() said to stop if service killed?
     boolean callStart;      // last onStart() has asked to alway be called on restart.
-    int lastStartId;        // identifier of most recent start request.
     int executeNesting;     // number of outstanding operations keeping foreground.
     long executingStart;    // start time of last execute request.
     int crashCount;         // number of times proc has crashed with service running
@@ -96,8 +95,11 @@
 
     String stringName;      // caching of toString
     
+    private int lastStartId;    // identifier of most recent start request.
+
     static class StartItem {
         final ServiceRecord sr;
+        final boolean taskRemoved;
         final int id;
         final Intent intent;
         final int targetPermissionUid;
@@ -108,8 +110,10 @@
 
         String stringName;      // caching of toString
 
-        StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) {
+        StartItem(ServiceRecord _sr, boolean _taskRemoved, int _id, Intent _intent,
+                int _targetPermissionUid) {
             sr = _sr;
+            taskRemoved = _taskRemoved;
             id = _id;
             intent = _intent;
             targetPermissionUid = _targetPermissionUid;
@@ -321,6 +325,18 @@
         return null;
     }
     
+    public int getLastStartId() {
+        return lastStartId;
+    }
+
+    public int makeNextStartId() {
+        lastStartId++;
+        if (lastStartId < 1) {
+            lastStartId = 1;
+        }
+        return lastStartId;
+    }
+
     public void postNotification() {
         final int appUid = appInfo.uid;
         final int appPid = app.pid;