am 11bba84c: am f1bb15d2: am 55953caa: Merge "Fix issue #16311398: Limit number of documents a process can open" into lmp-dev

* commit '11bba84c2da1c30e0d3b67456c04995d16a5db52':
  Fix issue #16311398: Limit number of documents a process can open
diff --git a/api/current.txt b/api/current.txt
index d6e1266..374f0ab 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3518,6 +3518,7 @@
     method public void postponeEnterTransition();
     method public void recreate();
     method public void registerForContextMenu(android.view.View);
+    method public boolean releaseInstance();
     method public final deprecated void removeDialog(int);
     method public void reportFullyDrawn();
     method public boolean requestVisibleBehind(boolean);
@@ -3637,7 +3638,9 @@
   public static class ActivityManager.AppTask {
     method public void finishAndRemoveTask();
     method public android.app.ActivityManager.RecentTaskInfo getTaskInfo();
+    method public void moveToFront();
     method public void setExcludeFromRecents(boolean);
+    method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
   }
 
   public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2e66a4c..9b7cc1c 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4704,6 +4704,26 @@
     }
 
     /**
+     * Ask that the local app instance of this activity be released to free up its memory.
+     * This is asking for the activity to be destroyed, but does <b>not</b> finish the activity --
+     * a new instance of the activity will later be re-created if needed due to the user
+     * navigating back to it.
+     *
+     * @return Returns true if the activity was in a state that it has started the process
+     * of destroying its current instance; returns false if for any reason this could not
+     * be done: it is currently visible to the user, it is already being destroyed, it is
+     * being finished, it hasn't yet saved its state, etc.
+     */
+    public boolean releaseInstance() {
+        try {
+            return ActivityManagerNative.getDefault().releaseActivityInstance(mToken);
+        } catch (RemoteException e) {
+            // Empty
+        }
+        return false;
+    }
+
+    /**
      * Called when an activity you launched exits, giving you the requestCode
      * you started it with, the resultCode it returned, and any additional
      * data from it.  The <var>resultCode</var> will be
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2173647..586e7d4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2623,6 +2623,40 @@
         }
 
         /**
+         * Bring this task to the foreground.  If it contains activities, they will be
+         * brought to the foreground with it and their instances re-created if needed.
+         * If it doesn't contain activities, the root activity of the task will be
+         * re-launched.
+         */
+        public void moveToFront() {
+            try {
+                mAppTaskImpl.moveToFront();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Invalid AppTask", e);
+            }
+        }
+
+        /**
+         * Start an activity in this task.  Brings the task to the foreground.  If this task
+         * is not currently active (that is, its id < 0), then the activity being started
+         * needs to be started as a new task and the Intent's ComponentName must match the
+         * base ComponenentName of the recent task entry.  Otherwise, the activity being
+         * started must <b>not</b> be launched as a new task -- not through explicit intent
+         * flags nor implicitly as the singleTask or singleInstance launch modes.
+         *
+         * <p>See {@link Activity#startActivity(android.content.Intent, android.os.Bundle)
+         * Activity.startActivity} for more information.</p>
+         *
+         * @param intent The Intent describing the new activity to be launched on the task.
+         * @param options Optional launch options.
+         */
+        public void startActivity(Context context, Intent intent, Bundle options) {
+            ActivityThread thread = ActivityThread.currentActivityThread();
+            thread.getInstrumentation().execStartActivityFromAppTask(context,
+                    thread.getApplicationThread(), mAppTaskImpl, intent, options);
+        }
+
+        /**
          * Modify the {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag in the root
          * Intent of this AppTask.
          *
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 82af99b..36e8892 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -365,6 +365,23 @@
             return true;
         }
 
+        case RELEASE_ACTIVITY_INSTANCE_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean res = releaseActivityInstance(token);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case RELEASE_SOME_ACTIVITIES_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IApplicationThread app = ApplicationThreadNative.asInterface(data.readStrongBinder());
+            releaseSomeActivities(app);
+            reply.writeNoException();
+            return true;
+        }
+
         case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
@@ -2661,6 +2678,28 @@
         data.recycle();
         reply.recycle();
     }
+    public boolean releaseActivityInstance(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(RELEASE_ACTIVITY_INSTANCE_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void releaseSomeActivities(IApplicationThread app) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(app.asBinder());
+        mRemote.transact(RELEASE_SOME_ACTIVITIES_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
     public boolean willActivityBeVisible(IBinder token) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2136209..0356093 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -102,8 +102,6 @@
 import com.android.org.conscrypt.TrustedCertificateStore;
 import com.google.android.collect.Lists;
 
-import dalvik.system.VMRuntime;
-
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
@@ -201,6 +199,7 @@
     String mInstrumentedLibDir = null;
     boolean mSystemThread = false;
     boolean mJitEnabled = false;
+    boolean mSomeActivitiesChanged = false;
 
     // These can be accessed by multiple threads; mPackages is the lock.
     // XXX For now we keep around information about all packages we have
@@ -2353,6 +2352,7 @@
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
+        mSomeActivitiesChanged = true;
 
         if (r.profileFd != null) {
             mProfiler.setProfiler(r.profileFile, r.profileFd);
@@ -2495,6 +2495,7 @@
     public void handleCancelVisibleBehind(IBinder token) {
         ActivityClientRecord r = mActivities.get(token);
         if (r != null) {
+            mSomeActivitiesChanged = true;
             final Activity activity = r.activity;
             if (activity.mVisibleBehind) {
                 activity.mCalled = false;
@@ -2984,6 +2985,7 @@
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
+        mSomeActivitiesChanged = true;
 
         // TODO Push resumeArgs into the activity for consideration
         ActivityClientRecord r = performResumeActivity(token, clearHide);
@@ -3175,6 +3177,7 @@
                 ActivityManagerNative.getDefault().activityPaused(token, r.persistentState);
             } catch (RemoteException ex) {
             }
+            mSomeActivitiesChanged = true;
         }
     }
 
@@ -3413,6 +3416,7 @@
         info.state = r.state;
         info.persistentState = r.persistentState;
         mH.post(info);
+        mSomeActivitiesChanged = true;
     }
 
     final void performRestartActivity(IBinder token) {
@@ -3446,6 +3450,7 @@
                 TAG, "Handle window " + r + " visibility: " + show);
             updateVisibility(r, show);
         }
+        mSomeActivitiesChanged = true;
     }
 
     private void handleSleeping(IBinder token, boolean sleeping) {
@@ -3743,6 +3748,7 @@
                 // If the system process has died, it's game over for everyone.
             }
         }
+        mSomeActivitiesChanged = true;
     }
 
     public final void requestRelaunchActivity(IBinder token,
@@ -3805,6 +3811,7 @@
         // If we are getting ready to gc after going to the background, well
         // we are back active so skip it.
         unscheduleGcIdler();
+        mSomeActivitiesChanged = true;
 
         Configuration changedConfig = null;
         int configChanges = 0;
@@ -4107,6 +4114,8 @@
         performConfigurationChanged(r.activity, mCompatConfiguration);
 
         freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
+
+        mSomeActivitiesChanged = true;
     }
 
     final void handleProfilerControl(boolean start, ProfilerControlData pcd, int profileType) {
@@ -5045,17 +5054,38 @@
             android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                     UserHandle.myUserId());
             RuntimeInit.setApplicationObject(mAppThread.asBinder());
-            IActivityManager mgr = ActivityManagerNative.getDefault();
+            final IActivityManager mgr = ActivityManagerNative.getDefault();
             try {
                 mgr.attachApplication(mAppThread);
             } catch (RemoteException ex) {
                 // Ignore
             }
+            // Watch for getting close to heap limit.
+            BinderInternal.addGcWatcher(new Runnable() {
+                @Override public void run() {
+                    if (!mSomeActivitiesChanged) {
+                        return;
+                    }
+                    Runtime runtime = Runtime.getRuntime();
+                    long dalvikMax = runtime.maxMemory();
+                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
+                    if (dalvikUsed > ((3*dalvikMax)/4)) {
+                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+                                + " total=" + (runtime.totalMemory()/1024)
+                                + " used=" + (dalvikUsed/1024));
+                        mSomeActivitiesChanged = false;
+                        try {
+                            mgr.releaseSomeActivities(mAppThread);
+                        } catch (RemoteException e) {
+                        }
+                    }
+                }
+            });
         } else {
             // Don't set application object here -- if the system crashes,
             // we can't display an alert, we just want to die die die.
             android.ddm.DdmHandleAppName.setAppName("system_process",
-                                                    UserHandle.myUserId());
+                    UserHandle.myUserId());
             try {
                 mInstrumentation = new Instrumentation();
                 ContextImpl context = ContextImpl.createAppContext(
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 69e1710..57c4b71 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -97,6 +97,8 @@
     public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
     public boolean finishActivityAffinity(IBinder token) throws RemoteException;
     public void finishVoiceTask(IVoiceInteractionSession session) throws RemoteException;
+    public boolean releaseActivityInstance(IBinder token) throws RemoteException;
+    public void releaseSomeActivities(IApplicationThread app) throws RemoteException;
     public boolean willActivityBeVisible(IBinder token) throws RemoteException;
     public Intent registerReceiver(IApplicationThread caller, String callerPackage,
             IIntentReceiver receiver, IntentFilter filter,
@@ -771,4 +773,6 @@
     int START_ACTIVITY_AS_CALLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+232;
     int ADD_APP_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+233;
     int GET_APP_TASK_THUMBNAIL_SIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+234;
+    int RELEASE_ACTIVITY_INSTANCE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+235;
+    int RELEASE_SOME_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+236;
 }
diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl
index 4e38c36..37fead9 100644
--- a/core/java/android/app/IAppTask.aidl
+++ b/core/java/android/app/IAppTask.aidl
@@ -17,10 +17,15 @@
 package android.app;
 
 import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
 
 /** @hide */
 interface IAppTask {
     void finishAndRemoveTask();
     ActivityManager.RecentTaskInfo getTaskInfo();
+    void moveToFront();
+    int startActivity(IBinder whoThread, String callingPackage,
+            in Intent intent, String resolvedType, in Bundle options);
     void setExcludeFromRecents(boolean exclude);
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index b28d7cc..bc71bad 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1702,6 +1702,40 @@
         return null;
     }
 
+    /**
+     * Special version!
+     * @hide
+     */
+    public void execStartActivityFromAppTask(
+            Context who, IBinder contextThread, IAppTask appTask,
+            Intent intent, Bundle options) {
+        IApplicationThread whoThread = (IApplicationThread) contextThread;
+        if (mActivityMonitors != null) {
+            synchronized (mSync) {
+                final int N = mActivityMonitors.size();
+                for (int i=0; i<N; i++) {
+                    final ActivityMonitor am = mActivityMonitors.get(i);
+                    if (am.match(who, null, intent)) {
+                        am.mHits++;
+                        if (am.isBlocking()) {
+                            return;
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+        try {
+            intent.migrateExtraStreamToClipData();
+            intent.prepareToLeaveProcess();
+            int result = appTask.startActivity(whoThread.asBinder(), who.getBasePackageName(),
+                    intent, intent.resolveTypeIfNeeded(who.getContentResolver()), options);
+            checkStartActivityResult(result, intent);
+        } catch (RemoteException e) {
+        }
+        return;
+    }
+
     /*package*/ final void init(ActivityThread thread,
             Context instrContext, Context appContext, ComponentName component, 
             IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7f56f2a..040986d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6320,8 +6320,7 @@
          * processes as soon as they are no longer needed.  If 0, the normal
          * extended lifetime is used.
          */
-        public static final String ALWAYS_FINISH_ACTIVITIES =
-                "always_finish_activities";
+        public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
 
         /**
          * Use Dock audio output for media:
diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java
index 3b0f0f4..240d9df 100644
--- a/core/java/com/android/internal/os/BinderInternal.java
+++ b/core/java/com/android/internal/os/BinderInternal.java
@@ -21,6 +21,7 @@
 import android.util.EventLog;
 
 import java.lang.ref.WeakReference;
+import java.util.ArrayList;
 
 /**
  * Private and debugging Binder APIs.
@@ -28,19 +29,35 @@
  * @see IBinder
  */
 public class BinderInternal {
-    static WeakReference<GcWatcher> mGcWatcher
+    static WeakReference<GcWatcher> sGcWatcher
             = new WeakReference<GcWatcher>(new GcWatcher());
-    static long mLastGcTime;
-    
+    static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
+    static Runnable[] sTmpWatchers = new Runnable[1];
+    static long sLastGcTime;
+
     static final class GcWatcher {
         @Override
         protected void finalize() throws Throwable {
             handleGc();
-            mLastGcTime = SystemClock.uptimeMillis();
-            mGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
+            sLastGcTime = SystemClock.uptimeMillis();
+            synchronized (sGcWatchers) {
+                sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
+            }
+            for (int i=0; i<sTmpWatchers.length; i++) {
+                if (sTmpWatchers[i] != null) {
+                    sTmpWatchers[i].run();
+                }
+            }
+            sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
         }
     }
-    
+
+    public static void addGcWatcher(Runnable watcher) {
+        synchronized (sGcWatchers) {
+            sGcWatchers.add(watcher);
+        }
+    }
+
     /**
      * Add the calling thread to the IPC thread pool.  This function does
      * not return until the current process is exiting.
@@ -58,7 +75,7 @@
      * SystemClock.uptimeMillis()} of the last garbage collection.
      */
     public static long getLastGcTime() {
-        return mLastGcTime;
+        return sLastGcTime;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index 8710aa2..0b36bdbd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -220,21 +220,9 @@
             // Bring an active task to the foreground
             mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts);
         } else {
-            // Launch the activity anew with the desired animation
-            boolean isDocument = Utilities.isDocument(toTask.key.baseIntent);
-            Intent intent = new Intent(toTask.key.baseIntent);
-            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
-                    | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-            if (!isDocument) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            }
             try {
                 mSystemServicesProxy.startActivityFromRecents(toTask.key.id, launchOpts);
             } catch (ActivityNotFoundException anfe) {}
-
-            // Remove the old task from activity manager
-            RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(toTask.key.id,
-                    isDocument);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 1dd484b..47fda5b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -458,13 +458,6 @@
                     // Bring an active task to the foreground
                     ssp.moveTaskToFront(task.key.id, launchOpts);
                 } else {
-                    // Launch the activity anew with the desired animation
-                    Intent i = new Intent(task.key.baseIntent);
-                    i.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
-                            | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-                    if (!Utilities.isDocument(i)) {
-                        i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-                    }
                     try {
                         ssp.startActivityFromRecents(task.key.id, launchOpts);
                         if (launchOpts == null && lockToTask) {
@@ -473,9 +466,6 @@
                     } catch (ActivityNotFoundException anfe) {
                         Console.logError(getContext(), "Could not start Activity");
                     }
-
-                    // And clean up the old task
-                    onTaskViewDismissed(task);
                 }
             }
         };
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 1ce073a..89e3f49 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -332,6 +332,7 @@
             final ArrayList<HandlerChecker> blockedCheckers;
             final String subject;
             final boolean allowRestart;
+            int debuggerWasConnected = 0;
             synchronized (this) {
                 long timeout = CHECK_INTERVAL;
                 // Make sure we (re)spin the checkers that have become idle within
@@ -341,17 +342,27 @@
                     hc.scheduleCheckLocked();
                 }
 
+                if (debuggerWasConnected > 0) {
+                    debuggerWasConnected--;
+                }
+
                 // NOTE: We use uptimeMillis() here because we do not want to increment the time we
                 // wait while asleep. If the device is asleep then the thing that we are waiting
                 // to timeout on is asleep as well and won't have a chance to run, causing a false
                 // positive on when to kill things.
                 long start = SystemClock.uptimeMillis();
                 while (timeout > 0) {
+                    if (Debug.isDebuggerConnected()) {
+                        debuggerWasConnected = 2;
+                    }
                     try {
                         wait(timeout);
                     } catch (InterruptedException e) {
                         Log.wtf(TAG, e);
                     }
+                    if (Debug.isDebuggerConnected()) {
+                        debuggerWasConnected = 2;
+                    }
                     timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start);
                 }
 
@@ -450,7 +461,12 @@
 
             // Only kill the process if the debugger is not attached.
             if (Debug.isDebuggerConnected()) {
+                debuggerWasConnected = 2;
+            }
+            if (debuggerWasConnected >= 2) {
                 Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process");
+            } else if (debuggerWasConnected > 0) {
+                Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process");
             } else if (!allowRestart) {
                 Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process");
             } else {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f315b74..e2a6534 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -33,6 +33,7 @@
 
 import android.Manifest;
 import android.app.AppOpsManager;
+import android.app.ApplicationThreadNative;
 import android.app.IActivityContainer;
 import android.app.IActivityContainerCallback;
 import android.app.IAppTask;
@@ -2831,8 +2832,7 @@
             if (proc.baseProcessTracker != null) {
                 proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
             }
-            killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss)
-                    + "k from cached");
+            proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
         } else if (proc != null && !keepIfLarge
                 && mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
                 && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
@@ -2841,8 +2841,7 @@
                 if (proc.baseProcessTracker != null) {
                     proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss);
                 }
-                killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss)
-                        + "k from cached");
+                proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true);
             }
         }
         return proc;
@@ -3318,7 +3317,8 @@
                     intent.setComponent(new ComponentName(
                             ri.activityInfo.packageName, ri.activityInfo.name));
                     mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo,
-                            null, null, null, null, 0, 0, 0, null, 0, null, false, null, null);
+                            null, null, null, null, 0, 0, 0, null, 0, null, false, null, null,
+                            null);
                 }
             }
         }
@@ -3462,7 +3462,7 @@
         // TODO: Switch to user app stacks here.
         return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
                 null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
-                null, null, options, userId, null);
+                null, null, options, userId, null, null);
     }
 
     @Override
@@ -3512,7 +3512,7 @@
             int ret = mStackSupervisor.startActivityMayWait(null, targetUid, targetPackage, intent,
                     resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                     null, null, null, null, options, UserHandle.getUserId(sourceRecord.app.uid),
-                    null);
+                    null, null);
             return ret;
         } catch (SecurityException e) {
             // XXX need to figure out how to propagate to original app.
@@ -3542,7 +3542,7 @@
         // TODO: Switch to user app stacks here.
         mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType,
                 null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
-                res, null, options, userId, null);
+                res, null, options, userId, null, null);
         return res;
     }
 
@@ -3557,7 +3557,7 @@
         // TODO: Switch to user app stacks here.
         int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
                 resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
-                null, null, null, config, options, userId, null);
+                null, null, null, config, options, userId, null, null);
         return ret;
     }
 
@@ -3615,7 +3615,7 @@
         // TODO: Switch to user app stacks here.
         return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent,
                 resolvedType, session, interactor, null, null, 0, startFlags,
-                profileFile, profileFd, null, null, options, userId, null);
+                profileFile, profileFd, null, null, options, userId, null, null);
     }
 
     @Override
@@ -3713,7 +3713,7 @@
             int res = mStackSupervisor.startActivityLocked(r.app.thread, intent,
                     r.resolvedType, aInfo, null, null, resultTo != null ? resultTo.appToken : null,
                     resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0,
-                    options, false, null, null);
+                    options, false, null, null, null);
             Binder.restoreCallingIdentity(origId);
 
             r.finishing = wasFinishing;
@@ -3732,36 +3732,42 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
+        return startActivityFromRecentsInner(taskId, options);
+    }
+
+    final int startActivityFromRecentsInner(int taskId, Bundle options) {
+        final TaskRecord task;
         final int callingUid;
         final String callingPackage;
         final Intent intent;
         final int userId;
         synchronized (this) {
-            final TaskRecord task = recentTaskForIdLocked(taskId);
+            task = recentTaskForIdLocked(taskId);
             if (task == null) {
-                throw new ActivityNotFoundException("Task " + taskId + " not found.");
+                throw new IllegalArgumentException("Task " + taskId + " not found.");
             }
             callingUid = task.mCallingUid;
             callingPackage = task.mCallingPackage;
             intent = task.intent;
+            intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
             userId = task.userId;
         }
         return startActivityInPackage(callingUid, callingPackage, intent, null, null, null, 0, 0,
-                options, userId, null);
+                options, userId, null, task);
     }
 
     final int startActivityInPackage(int uid, String callingPackage,
             Intent intent, String resolvedType, IBinder resultTo,
             String resultWho, int requestCode, int startFlags, Bundle options, int userId,
-                    IActivityContainer container) {
+            IActivityContainer container, TaskRecord inTask) {
 
         userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId,
                 false, ALLOW_FULL_ONLY, "startActivityInPackage", null);
 
         // TODO: Switch to user app stacks here.
-        int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType,
-                null, null, resultTo, resultWho, requestCode, startFlags,
-                null, null, null, null, options, userId, container);
+        int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent,
+                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
+                null, null, null, null, options, userId, container, inTask);
         return ret;
     }
 
@@ -4155,6 +4161,35 @@
     }
 
     @Override
+    public boolean releaseActivityInstance(IBinder token) {
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                ActivityRecord r = ActivityRecord.isInStackLocked(token);
+                if (r.task == null || r.task.stack == null) {
+                    return false;
+                }
+                return r.task.stack.safelyDestroyActivityLocked(r, "app-req");
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
+    public void releaseSomeActivities(IApplicationThread appInt) {
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                ProcessRecord app = getRecordForAppLocked(appInt);
+                mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem");
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
+        }
+    }
+
+    @Override
     public boolean willActivityBeVisible(IBinder token) {
         synchronized(this) {
             ActivityStack stack = ActivityRecord.getStackLocked(token);
@@ -4568,8 +4603,7 @@
                 // 0 == continue, -1 = kill process immediately
                 int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);
                 if (res < 0 && app.pid != MY_PID) {
-                    Process.killProcess(app.pid);
-                    Process.killProcessGroup(app.info.uid, app.pid);
+                    app.kill("anr", true);
                 }
             } catch (RemoteException e) {
                 mController = null;
@@ -4675,8 +4709,7 @@
                 int res = mController.appNotResponding(app.processName, app.pid, info.toString());
                 if (res != 0) {
                     if (res < 0 && app.pid != MY_PID) {
-                        Process.killProcess(app.pid);
-                        Process.killProcessGroup(app.info.uid, app.pid);
+                        app.kill("anr", true);
                     } else {
                         synchronized (this) {
                             mServices.scheduleServiceTimeoutLocked(app);
@@ -4696,7 +4729,7 @@
 
         synchronized (this) {
             if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
-                killUnneededProcessLocked(app, "background ANR");
+                app.kill("bg anr", true);
                 return;
             }
 
@@ -5420,8 +5453,7 @@
             if (app.isolated) {
                 mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
             }
-            killUnneededProcessLocked(app, reason);
-            Process.killProcessGroup(app.info.uid, app.pid);
+            app.kill(reason, true);
             handleAppDiedLocked(app, true, allowRestart);
             removeLruProcessLocked(app);
 
@@ -5469,7 +5501,7 @@
             checkAppInLaunchingProvidersLocked(app, true);
             // Take care of any services that are waiting for the process.
             mServices.processStartTimedOutLocked(app);
-            killUnneededProcessLocked(app, "start timeout");
+            app.kill("start timeout", true);
             if (mBackupTarget != null && mBackupTarget.app.pid == pid) {
                 Slog.w(TAG, "Unattached app died before backup, skipping");
                 try {
@@ -7921,17 +7953,6 @@
         }
     }
 
-    private void killUnneededProcessLocked(ProcessRecord pr, String reason) {
-        if (!pr.killedByAm) {
-            Slog.i(TAG, "Killing " + pr.toShortString() + " (adj " + pr.setAdj + "): " + reason);
-            EventLog.writeEvent(EventLogTags.AM_KILL, pr.userId, pr.pid,
-                    pr.processName, pr.setAdj, reason);
-            pr.killedByAm = true;
-            Process.killProcessQuiet(pr.pid);
-            Process.killProcessGroup(pr.info.uid, pr.pid);
-        }
-    }
-
     private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) {
         tr.disposeThumbnail();
         mRecentTasks.remove(tr);
@@ -7975,7 +7996,7 @@
                     continue;
                 }
                 if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
-                    killUnneededProcessLocked(pr, "remove task");
+                    pr.kill("remove task", true);
                 } else {
                     pr.waitingToKill = "remove task";
                 }
@@ -8028,32 +8049,36 @@
 
         if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving taskId=" + taskId);
         synchronized(this) {
-            if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
-                    Binder.getCallingUid(), "Task to front")) {
-                ActivityOptions.abort(options);
+            moveTaskToFrontLocked(taskId, flags, options);
+        }
+    }
+
+    void moveTaskToFrontLocked(int taskId, int flags, Bundle options) {
+        if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+                Binder.getCallingUid(), "Task to front")) {
+            ActivityOptions.abort(options);
+            return;
+        }
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+            if (task == null) {
                 return;
             }
-            final long origId = Binder.clearCallingIdentity();
-            try {
-                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
-                if (task == null) {
-                    return;
-                }
-                if (mStackSupervisor.isLockTaskModeViolation(task)) {
-                    mStackSupervisor.showLockTaskToast();
-                    Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
-                    return;
-                }
-                final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
-                if (prev != null && prev.isRecentsActivity()) {
-                    task.setTaskToReturnTo(ActivityRecord.RECENTS_ACTIVITY_TYPE);
-                }
-                mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options);
-            } finally {
-                Binder.restoreCallingIdentity(origId);
+            if (mStackSupervisor.isLockTaskModeViolation(task)) {
+                mStackSupervisor.showLockTaskToast();
+                Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode");
+                return;
             }
-            ActivityOptions.abort(options);
+            final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked();
+            if (prev != null && prev.isRecentsActivity()) {
+                task.setTaskToReturnTo(ActivityRecord.RECENTS_ACTIVITY_TYPE);
+            }
+            mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options);
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
+        ActivityOptions.abort(options);
     }
 
     @Override
@@ -10151,7 +10176,7 @@
                 }
                 int adj = proc.setAdj;
                 if (adj >= worstType && !proc.killedByAm) {
-                    killUnneededProcessLocked(proc, reason);
+                    proc.kill(reason, true);
                     killed = true;
                 }
             }
@@ -10195,7 +10220,7 @@
 
                 final int adj = proc.setAdj;
                 if (adj > belowAdj && !proc.killedByAm) {
-                    killUnneededProcessLocked(proc, reason);
+                    proc.kill(reason, true);
                     killed = true;
                 }
             }
@@ -10314,8 +10339,8 @@
                             && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
                         if (doKilling && proc.initialIdlePss != 0
                                 && proc.lastPss > ((proc.initialIdlePss*3)/2)) {
-                            killUnneededProcessLocked(proc, "idle maint (pss " + proc.lastPss
-                                    + " from " + proc.initialIdlePss + ")");
+                            proc.kill("idle maint (pss " + proc.lastPss
+                                    + " from " + proc.initialIdlePss + ")", true);
                         }
                     }
                 } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME) {
@@ -10784,7 +10809,7 @@
             }
             if (app.pid > 0 && app.pid != MY_PID) {
                 handleAppCrashLocked(app, null, null, null);
-                killUnneededProcessLocked(app, "user request after error");
+                app.kill("user request after error", true);
             }
         }
     }
@@ -11351,8 +11376,11 @@
                         } else {
                             Slog.w(TAG, "Force-killing crashed app " + name
                                     + " at watcher's request");
-                            Process.killProcess(pid);
                             if (r != null) {
+                                r.kill("crash", true);
+                            } else {
+                                // Huh.
+                                Process.killProcess(pid);
                                 Process.killProcessGroup(uid, pid);
                             }
                         }
@@ -13701,9 +13729,9 @@
                 if (!capp.persistent && capp.thread != null
                         && capp.pid != 0
                         && capp.pid != MY_PID) {
-                    killUnneededProcessLocked(capp, "depends on provider "
+                    capp.kill("depends on provider "
                             + cpr.name.flattenToShortString()
-                            + " in dying proc " + (proc != null ? proc.processName : "??"));
+                            + " in dying proc " + (proc != null ? proc.processName : "??"), true);
                 }
             } else if (capp.thread != null && conn.provider.provider != null) {
                 try {
@@ -16559,8 +16587,7 @@
                         stats.reportExcessiveWakeLocked(app.info.uid, app.processName,
                                 realtimeSince, wtimeUsed);
                     }
-                    killUnneededProcessLocked(app, "excessive wake held " + wtimeUsed
-                            + " during " + realtimeSince);
+                    app.kill("excessive wake held " + wtimeUsed + " during " + realtimeSince, true);
                     app.baseProcessTracker.reportExcessiveWake(app.pkgList);
                 } else if (doCpuKills && uptimeSince > 0
                         && ((cputimeUsed*100)/uptimeSince) >= 25) {
@@ -16568,8 +16595,7 @@
                         stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
                                 uptimeSince, cputimeUsed);
                     }
-                    killUnneededProcessLocked(app, "excessive cpu " + cputimeUsed
-                            + " during " + uptimeSince);
+                    app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince, true);
                     app.baseProcessTracker.reportExcessiveCpu(app.pkgList);
                 } else {
                     app.lastWakeTime = wtime;
@@ -16604,7 +16630,7 @@
                     + " to " + app.curSchedGroup);
             if (app.waitingToKill != null &&
                     app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) {
-                killUnneededProcessLocked(app, app.waitingToKill);
+                app.kill(app.waitingToKill, true);
                 success = false;
             } else {
                 if (true) {
@@ -16984,19 +17010,19 @@
                         mNumCachedHiddenProcs++;
                         numCached++;
                         if (numCached > cachedProcessLimit) {
-                            killUnneededProcessLocked(app, "cached #" + numCached);
+                            app.kill("cached #" + numCached, true);
                         }
                         break;
                     case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                         if (numEmpty > ProcessList.TRIM_EMPTY_APPS
                                 && app.lastActivityTime < oldTime) {
-                            killUnneededProcessLocked(app, "empty for "
+                            app.kill("empty for "
                                     + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
-                                    / 1000) + "s");
+                                    / 1000) + "s", true);
                         } else {
                             numEmpty++;
                             if (numEmpty > emptyProcessLimit) {
-                                killUnneededProcessLocked(app, "empty #" + numEmpty);
+                                app.kill("empty #" + numEmpty, true);
                             }
                         }
                         break;
@@ -17012,7 +17038,7 @@
                     // definition not re-use the same process again, and it is
                     // good to avoid having whatever code was running in them
                     // left sitting around after no longer needed.
-                    killUnneededProcessLocked(app, "isolated not needed");
+                    app.kill("isolated not needed", true);
                 }
 
                 if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME
@@ -17240,11 +17266,7 @@
                         + (app.thread != null ? app.thread.asBinder() : null)
                         + ")\n");
                     if (app.pid > 0 && app.pid != MY_PID) {
-                        EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid,
-                                app.processName, app.setAdj, "empty");
-                        app.killedByAm = true;
-                        Process.killProcessQuiet(app.pid);
-                        Process.killProcessGroup(app.info.uid, app.pid);
+                        app.kill("empty", false);
                     } else {
                         try {
                             app.thread.scheduleExit();
@@ -18308,14 +18330,15 @@
                 long origId = Binder.clearCallingIdentity();
                 try {
                     TaskRecord tr = recentTaskForIdLocked(mTaskId);
-                    if (tr != null) {
-                        // Only kill the process if we are not a new document
-                        int flags = tr.getBaseIntent().getFlags();
-                        boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
-                                Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-                        removeTaskByIdLocked(mTaskId,
-                                !isDocument ? ActivityManager.REMOVE_TASK_KILL_PROCESS : 0);
+                    if (tr == null) {
+                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                     }
+                    // Only kill the process if we are not a new document
+                    int flags = tr.getBaseIntent().getFlags();
+                    boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
+                            Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+                    removeTaskByIdLocked(mTaskId,
+                            !isDocument ? ActivityManager.REMOVE_TASK_KILL_PROCESS : 0);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
@@ -18330,17 +18353,64 @@
                 long origId = Binder.clearCallingIdentity();
                 try {
                     TaskRecord tr = recentTaskForIdLocked(mTaskId);
-                    if (tr != null) {
-                        return createRecentTaskInfoFromTaskRecord(tr);
+                    if (tr == null) {
+                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                     }
+                    return createRecentTaskInfoFromTaskRecord(tr);
                 } finally {
                     Binder.restoreCallingIdentity(origId);
                 }
-                return null;
             }
         }
 
         @Override
+        public void moveToFront() {
+            checkCaller();
+
+            final TaskRecord tr;
+            synchronized (ActivityManagerService.this) {
+                tr = recentTaskForIdLocked(mTaskId);
+                if (tr == null) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+                if (tr.getRootActivity() != null) {
+                    long origId = Binder.clearCallingIdentity();
+                    try {
+                        moveTaskToFrontLocked(tr.taskId, 0, null);
+                        return;
+                    } finally {
+                        Binder.restoreCallingIdentity(origId);
+                    }
+                }
+            }
+
+            startActivityFromRecentsInner(tr.taskId, null);
+        }
+
+        @Override
+        public int startActivity(IBinder whoThread, String callingPackage,
+                Intent intent, String resolvedType, Bundle options) {
+            checkCaller();
+
+            int callingUser = UserHandle.getCallingUserId();
+            TaskRecord tr;
+            IApplicationThread appThread;
+            synchronized (ActivityManagerService.this) {
+                tr = recentTaskForIdLocked(mTaskId);
+                if (tr == null) {
+                    throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                }
+                appThread = ApplicationThreadNative.asInterface(whoThread);
+                if (appThread == null) {
+                    throw new IllegalArgumentException("Bad app thread " + appThread);
+                }
+            }
+            return mStackSupervisor.startActivityMayWait(appThread, -1, callingPackage, intent,
+                    resolvedType, null, null, null, null, 0, 0, null, null,
+                    null, null, options, callingUser, null, tr);
+        }
+
+        @Override
         public void setExcludeFromRecents(boolean exclude) {
             checkCaller();
 
@@ -18348,14 +18418,15 @@
                 long origId = Binder.clearCallingIdentity();
                 try {
                     TaskRecord tr = recentTaskForIdLocked(mTaskId);
-                    if (tr != null) {
-                        Intent intent = tr.getBaseIntent();
-                        if (exclude) {
-                            intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                        } else {
-                            intent.setFlags(intent.getFlags()
-                                    & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-                        }
+                    if (tr == null) {
+                        throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
+                    }
+                    Intent intent = tr.getBaseIntent();
+                    if (exclude) {
+                        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                    } else {
+                        intent.setFlags(intent.getFlags()
+                                & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(origId);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 39b6375..066ec68 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1070,6 +1070,24 @@
         return null;
     }
 
+    final boolean isDestroyable() {
+        if (finishing || app == null || state == ActivityState.DESTROYING
+                || state == ActivityState.DESTROYED) {
+            // This would be redundant.
+            return false;
+        }
+        if (task == null || task.stack == null || this == task.stack.mResumedActivity
+                || this == task.stack.mPausingActivity || !haveState || !stopped) {
+            // We're not ready for this kind of thing.
+            return false;
+        }
+        if (visible) {
+            // The user would notice this!
+            return false;
+        }
+        return true;
+    }
+
     private static String createImageFilename(long createTime, int taskId) {
         return String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX + createTime +
                 TaskPersister.IMAGE_EXTENSION;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 41ed4ce7..fd1474f 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -36,11 +36,13 @@
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_CONTAINERS;
+import static com.android.server.am.ActivityStackSupervisor.DEBUG_RELEASE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_SCREENSHOTS;
 import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
 import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
 
+import android.util.ArraySet;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.Watchdog;
@@ -2910,7 +2912,7 @@
                     int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent,
                             null, aInfo, null, null, parent.appToken, null,
                             0, -1, parent.launchedFromUid, parent.launchedFromPackage,
-                            0, null, true, null, null);
+                            0, null, true, null, null, null);
                     foundParentInTask = res == ActivityManager.START_SUCCESS;
                 } catch (RemoteException e) {
                     foundParentInTask = false;
@@ -3058,15 +3060,10 @@
                 if (!lastIsOpaque) {
                     continue;
                 }
-                // We can destroy this one if we have its icicle saved and
-                // it is not in the process of pausing/stopping/finishing.
-                if (r.app != null && r != mResumedActivity && r != mPausingActivity
-                        && r.haveState && !r.visible && r.stopped
-                        && r.state != ActivityState.DESTROYING
-                        && r.state != ActivityState.DESTROYED) {
+                if (r.isDestroyable()) {
                     if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state
                             + " resumed=" + mResumedActivity
-                            + " pausing=" + mPausingActivity);
+                            + " pausing=" + mPausingActivity + " for reason " + reason);
                     if (destroyActivityLocked(r, true, reason)) {
                         activityRemoved = true;
                     }
@@ -3078,6 +3075,60 @@
         }
     }
 
+    final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) {
+        if (r.isDestroyable()) {
+            if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state
+                    + " resumed=" + mResumedActivity
+                    + " pausing=" + mPausingActivity + " for reason " + reason);
+            return destroyActivityLocked(r, true, reason);
+        }
+        return false;
+    }
+
+    final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks,
+            String reason) {
+        // Iterate over tasks starting at the back (oldest) first.
+        if (DEBUG_RELEASE) Slog.d(TAG, "Trying to release some activities in " + app);
+        int maxTasks = tasks.size() / 4;
+        if (maxTasks < 1) {
+            maxTasks = 1;
+        }
+        int numReleased = 0;
+        for (int taskNdx = 0; taskNdx < mTaskHistory.size() && maxTasks > 0; taskNdx++) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            if (!tasks.contains(task)) {
+                continue;
+            }
+            if (DEBUG_RELEASE) Slog.d(TAG, "Looking for activities to release in " + task);
+            int curNum = 0;
+            final ArrayList<ActivityRecord> activities = task.mActivities;
+            for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
+                final ActivityRecord activity = activities.get(actNdx);
+                if (activity.app == app && activity.isDestroyable()) {
+                    if (DEBUG_RELEASE) Slog.v(TAG, "Destroying " + activity
+                            + " in state " + activity.state + " resumed=" + mResumedActivity
+                            + " pausing=" + mPausingActivity + " for reason " + reason);
+                    destroyActivityLocked(activity, true, reason);
+                    if (activities.get(actNdx) != activity) {
+                        // Was removed from list, back up so we don't miss the next one.
+                        actNdx--;
+                    }
+                    curNum++;
+                }
+            }
+            if (curNum > 0) {
+                numReleased += curNum;
+                maxTasks--;
+                if (mTaskHistory.get(taskNdx) != task) {
+                    // The entire task got removed, back up so we don't miss the next one.
+                    taskNdx--;
+                }
+            }
+        }
+        if (DEBUG_RELEASE) Slog.d(TAG, "Done releasing: did " + numReleased + " activities");
+        return numReleased;
+    }
+
     /**
      * Destroy the current CLIENT SIDE instance of an activity.  This may be
      * called both when actually finishing an activity, or when performing
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index bd8501c..a9898ee 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -84,6 +84,7 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.voice.IVoiceInteractionSession;
+import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -115,10 +116,11 @@
     static final boolean DEBUG_APP = DEBUG || false;
     static final boolean DEBUG_CONTAINERS = DEBUG || false;
     static final boolean DEBUG_IDLE = DEBUG || false;
-    static final boolean DEBUG_VISIBLE_BEHIND = DEBUG || false;
+    static final boolean DEBUG_RELEASE = DEBUG || false;
     static final boolean DEBUG_SAVED_STATE = DEBUG || false;
     static final boolean DEBUG_SCREENSHOTS = DEBUG || false;
     static final boolean DEBUG_STATES = DEBUG || false;
+    static final boolean DEBUG_VISIBLE_BEHIND = DEBUG || false;
 
     public static final int HOME_STACK_ID = 0;
 
@@ -781,7 +783,7 @@
     void startHomeActivity(Intent intent, ActivityInfo aInfo) {
         moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE);
         startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0,
-                null, false, null, null);
+                null, false, null, null, null);
     }
 
     final int startActivityMayWait(IApplicationThread caller, int callingUid,
@@ -789,7 +791,7 @@
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile,
             ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
-            Bundle options, int userId, IActivityContainer iContainer) {
+            Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -899,7 +901,7 @@
             int res = startActivityLocked(caller, intent, resolvedType, aInfo,
                     voiceSession, voiceInteractor, resultTo, resultWho,
                     requestCode, callingPid, callingUid, callingPackage, startFlags, options,
-                    componentSpecified, null, container);
+                    componentSpecified, null, container, inTask);
 
             Binder.restoreCallingIdentity(origId);
 
@@ -1014,7 +1016,7 @@
                     }
                     int res = startActivityLocked(caller, intent, resolvedTypes[i],
                             aInfo, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage,
-                            0, theseOptions, componentSpecified, outActivity, null);
+                            0, theseOptions, componentSpecified, outActivity, null, null);
                     if (res < 0) {
                         return res;
                     }
@@ -1241,7 +1243,8 @@
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             IBinder resultTo, String resultWho, int requestCode,
             int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options,
-            boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container) {
+            boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container,
+            TaskRecord inTask) {
         int err = ActivityManager.START_SUCCESS;
 
         ProcessRecord callerApp = null;
@@ -1453,7 +1456,7 @@
         doPendingActivityLaunchesLocked(false);
 
         err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
-                startFlags, true, options);
+                startFlags, true, options, inTask);
 
         if (err < 0) {
             // If someone asked to have the keyguard dismissed on the next
@@ -1536,10 +1539,9 @@
         }
     }
 
-    final int startActivityUncheckedLocked(ActivityRecord r,
-            ActivityRecord sourceRecord,
+    final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags,
-            boolean doResume, Bundle options) {
+            boolean doResume, Bundle options, TaskRecord inTask) {
         final Intent intent = r.intent;
         final int callingUid = r.launchedFromUid;
 
@@ -1571,8 +1573,9 @@
             }
         }
 
-        final boolean launchTaskBehind = r.mLaunchTaskBehind &&
-                (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
+        final boolean launchTaskBehind = r.mLaunchTaskBehind
+                && !launchSingleTask && !launchSingleInstance
+                && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
 
         if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
             // For whatever reason this activity is being launched into a new
@@ -1591,6 +1594,15 @@
             launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
         }
 
+        // If we are actually going to launch in to a new task, there are some cases where
+        // we further want to do multiple task.
+        if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+            if (launchTaskBehind
+                    || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) {
+                launchFlags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+            }
+        }
+
         // We'll invoke onUserLeaving before onPause only if the launching
         // activity did not explicitly state that this is an automated launch.
         mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
@@ -1624,7 +1636,7 @@
         if (sourceRecord == null) {
             // This activity is not being started from another...  in this
             // case we -always- start a new task.
-            if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+            if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
                 Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                         "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
                 launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1642,7 +1654,7 @@
 
         ActivityInfo newTaskInfo = null;
         Intent newTaskIntent = null;
-        final ActivityStack sourceStack;
+        ActivityStack sourceStack;
         if (sourceRecord != null) {
             if (sourceRecord.finishing) {
                 // If the source is finishing, we can't further count it as our source.  This
@@ -1666,12 +1678,51 @@
             sourceStack = null;
         }
 
-        intent.setFlags(launchFlags);
-
         boolean addingToTask = false;
         boolean movedHome = false;
         TaskRecord reuseTask = null;
         ActivityStack targetStack;
+
+        intent.setFlags(launchFlags);
+
+        // If the caller is not coming from another activity, but has given us an
+        // explicit task into which they would like us to launch the new activity,
+        // then let's see about doing that.
+        if (sourceRecord == null && inTask != null && inTask.stack != null) {
+            // If this task is empty, then we are adding the first activity -- it
+            // determines the root, and must be launching as a NEW_TASK.
+            if (inTask.getRootActivity() == null) {
+                if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+                        && !launchSingleInstance && !launchSingleTask) {
+                    throw new IllegalStateException("Caller has inTask " + inTask
+                            + " but target is not a new task");
+                } else if (inTask.getBaseIntent() == null || !intent.getComponent().equals(
+                        inTask.getBaseIntent().getComponent())) {
+                    throw new IllegalStateException("Caller requested " + inTask + " is component "
+                            + inTask.getBaseIntent() + " but starting " + intent);
+                }
+                inTask.setIntent(r);
+
+            // If the task is not empty, then we are going to add the new activity on top
+            // of the task, so it can not be launching as a new task.
+            } else {
+                if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0
+                        || launchSingleInstance || launchSingleTask) {
+                    throw new IllegalStateException("Caller has inTask " + inTask
+                            + " but target is a new task");
+                }
+            }
+            sourceStack = inTask.stack;
+            reuseTask = inTask;
+        } else {
+            inTask = null;
+        }
+
+        // We may want to try to place the new activity in to an existing task.  We always
+        // do this if the target activity is singleTask or singleInstance; we will also do
+        // this if NEW_TASK has been requested, and there is not an additional qualifier telling
+        // us to still place it in a new task: multi task, always doc mode, or being asked to
+        // launch this as a new task behind the current one.
         if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
                 (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                 || launchSingleInstance || launchSingleTask) {
@@ -1908,8 +1959,13 @@
                 Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
                 return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
             }
-            newTask = true;
-            targetStack = adjustStackFocus(r, newTask);
+            if (inTask == null) {
+                // If we have an incoming task, we are just going to use that.
+                newTask = true;
+                targetStack = adjustStackFocus(r, newTask);
+            } else {
+                targetStack = inTask.stack;
+            }
             if (!launchTaskBehind) {
                 targetStack.moveToFront();
             }
@@ -1986,6 +2042,20 @@
             if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
                     + " in existing task " + r.task + " from source " + sourceRecord);
 
+        } else if (inTask != null) {
+            // The calling is asking that the new activity be started in an explicit
+            // task it has provided to us.
+            if (isLockTaskModeViolation(inTask)) {
+                Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
+                return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+            }
+            targetStack = inTask.stack;
+            targetStack.moveToFront();
+            mWindowManager.moveTaskToTop(targetStack.topTask().taskId);
+            r.setTask(inTask, null);
+            if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r
+                    + " in explicit task " + r.task);
+
         } else {
             // This not being started from an existing activity, and not part
             // of a new task...  just put it in the top task, though these days
@@ -2023,7 +2093,7 @@
         while (!mPendingActivityLaunches.isEmpty()) {
             PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
             startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags,
-                    doResume && mPendingActivityLaunches.isEmpty(), null);
+                    doResume && mPendingActivityLaunches.isEmpty(), null, null);
         }
     }
 
@@ -2725,6 +2795,64 @@
         }
     }
 
+    void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
+        // Examine all activities currently running in the process.
+        TaskRecord firstTask = null;
+        // Tasks is non-null only if two or more tasks are found.
+        ArraySet<TaskRecord> tasks = null;
+        if (DEBUG_RELEASE) Slog.d(TAG, "Trying to release some activities in " + app);
+        for (int i=0; i<app.activities.size(); i++) {
+            ActivityRecord r = app.activities.get(i);
+            // First, if we find an activity that is in the process of being destroyed,
+            // then we just aren't going to do anything for now; we want things to settle
+            // down before we try to prune more activities.
+            if (r.finishing || r.state == ActivityState.DESTROYING
+                    || r.state == ActivityState.DESTROYED) {
+                if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r);
+                return;
+            }
+            // Don't consider any activies that are currently not in a state where they
+            // can be destroyed.
+            if (r.visible || !r.stopped || !r.haveState
+                    || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING
+                    || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING) {
+                if (DEBUG_RELEASE) Slog.d(TAG, "Not releasing in-use activity: " + r);
+                continue;
+            }
+            if (r.task != null) {
+                if (DEBUG_RELEASE) Slog.d(TAG, "Collecting release task " + r.task
+                        + " from " + r);
+                if (firstTask == null) {
+                    firstTask = r.task;
+                } else if (firstTask != r.task) {
+                    if (tasks == null) {
+                        tasks = new ArraySet<>();
+                        tasks.add(firstTask);
+                    }
+                    tasks.add(r.task);
+                }
+            }
+        }
+        if (tasks == null) {
+            if (DEBUG_RELEASE) Slog.d(TAG, "Didn't find two or more tasks to release");
+            return;
+        }
+        // If we have activities in multiple tasks that are in a position to be destroyed,
+        // let's iterate through the tasks and release the oldest one.
+        final int numDisplays = mActivityDisplays.size();
+        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
+            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
+            // Step through all stacks starting from behind, to hit the oldest things first.
+            for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
+                final ActivityStack stack = stacks.get(stackNdx);
+                // Try to release activities in this stack; if we manage to, we are done.
+                if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
+                    return;
+                }
+            }
+        }
+    }
+
     boolean switchUserLocked(int userId, UserStartedState uss) {
         mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId());
         final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
@@ -3498,7 +3626,7 @@
                 mimeType = mService.getProviderMimeType(intent.getData(), userId);
             }
             return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, 0, null,
-                    null, null, null, null, userId, this);
+                    null, null, null, null, userId, this, null);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 98999e9..433ab60 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -254,7 +254,7 @@
                             } else {
                                 owner.startActivityInPackage(uid, key.packageName, finalIntent,
                                         resolvedType, resultTo, resultWho, requestCode, 0,
-                                        options, userId, container);
+                                        options, userId, container, null);
                             }
                         } catch (RuntimeException e) {
                             Slog.w(ActivityManagerService.TAG,
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index f1bcb60..0817dd8 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -17,6 +17,8 @@
 package com.android.server.am;
 
 import android.util.ArraySet;
+import android.util.EventLog;
+import android.util.Slog;
 import com.android.internal.app.ProcessStats;
 import com.android.internal.os.BatteryStatsImpl;
 
@@ -502,6 +504,21 @@
         return adj;
     }
 
+    void kill(String reason, boolean noisy) {
+        if (!killedByAm) {
+            if (noisy) {
+                Slog.i(ActivityManagerService.TAG, "Killing " + toShortString() + " (adj " + setAdj
+                        + "): " + reason);
+            }
+            EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason);
+            Process.killProcessQuiet(pid);
+            Process.killProcessGroup(info.uid, pid);
+            if (!persistent) {
+                killedByAm = true;
+            }
+        }
+    }
+
     public String toShortString() {
         if (shortStringName != null) {
             return shortStringName;
diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml
index c0898d30..3fb547d 100644
--- a/tests/ActivityTests/AndroidManifest.xml
+++ b/tests/ActivityTests/AndroidManifest.xml
@@ -31,6 +31,11 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name="SpamActivity" android:label="Spam!"
+                android:documentLaunchMode="always">
+        </activity>
+        <activity android:name="DocActivity" android:label="Some doc">
+        </activity>
         <service android:name="SingleUserService"
             android:singleUser="true" android:exported="true">
         </service>
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
index 7f3aa77..ea0db56 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -21,6 +21,7 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
@@ -29,13 +30,15 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.graphics.BitmapFactory;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.graphics.Bitmap;
-import android.view.WindowManager;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -60,6 +63,28 @@
 
     ArrayList<ServiceConnection> mConnections = new ArrayList<ServiceConnection>();
 
+    static final int MSG_SPAM = 1;
+
+    final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SPAM: {
+                    boolean fg = msg.arg1 != 0;
+                    Intent intent = new Intent(ActivityTestMain.this, SpamActivity.class);
+                    Bundle options = null;
+                    if (fg) {
+                        ActivityOptions opts = ActivityOptions.makeLaunchTaskBehindAnimation();
+                        options = opts.toBundle();
+                    }
+                    startActivity(intent, options);
+                    scheduleSpam(!fg);
+                } break;
+            }
+            super.handleMessage(msg);
+        }
+    };
+
     class BroadcastResultReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -143,7 +168,8 @@
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         menu.add("Animate!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
-            @Override public boolean onMenuItemClick(MenuItem item) {
+            @Override
+            public boolean onMenuItemClick(MenuItem item) {
                 AlertDialog.Builder builder = new AlertDialog.Builder(ActivityTestMain.this,
                         R.style.SlowDialog);
                 builder.setTitle("This is a title");
@@ -316,6 +342,49 @@
                 return true;
             }
         });
+        menu.add("Open Doc").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                ActivityManager.AppTask task = findDocTask();
+                if (task == null) {
+                    Intent intent = new Intent(ActivityTestMain.this, DocActivity.class);
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                            | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+                            | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+                    startActivity(intent);
+                } else {
+                    task.moveToFront();
+                }
+                return true;
+            }
+        });
+        menu.add("Stack Doc").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                ActivityManager.AppTask task = findDocTask();
+                if (task != null) {
+                    ActivityManager.RecentTaskInfo recent = task.getTaskInfo();
+                    Intent intent = new Intent(ActivityTestMain.this, DocActivity.class);
+                    if (recent.id >= 0) {
+                        // Stack on top.
+                        intent.putExtra(DocActivity.LABEL, "Stacked");
+                        task.startActivity(ActivityTestMain.this, intent, null);
+                    } else {
+                        // Start root activity.
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                                | Intent.FLAG_ACTIVITY_MULTIPLE_TASK
+                                | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+                        intent.putExtra(DocActivity.LABEL, "New Root");
+                        task.startActivity(ActivityTestMain.this, intent, null);
+                    }
+                }
+                return true;
+            }
+        });
+        menu.add("Spam!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+            @Override public boolean onMenuItemClick(MenuItem item) {
+                scheduleSpam(false);
+                return true;
+            }
+        });
         return true;
     }
 
@@ -342,6 +411,12 @@
         mConnections.clear();
     }
 
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mHandler.removeMessages(MSG_SPAM);
+    }
+
     void addAppRecents(int count) {
         ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
         Intent intent = new Intent(Intent.ACTION_MAIN);
@@ -371,7 +446,31 @@
             }
         }
     }
-     private View scrollWrap(View view) {
+
+    ActivityManager.AppTask findDocTask() {
+        ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+        List<ActivityManager.AppTask> tasks = am.getAppTasks();
+        if (tasks != null) {
+            for (int i=0; i<tasks.size(); i++) {
+                ActivityManager.AppTask task = tasks.get(i);
+                ActivityManager.RecentTaskInfo recent = task.getTaskInfo();
+                if (recent.baseIntent != null
+                        && recent.baseIntent.getComponent().getClassName().equals(
+                                DocActivity.class.getCanonicalName())) {
+                    return task;
+                }
+            }
+        }
+        return null;
+    }
+
+    void scheduleSpam(boolean fg) {
+        mHandler.removeMessages(MSG_SPAM);
+        Message msg = mHandler.obtainMessage(MSG_SPAM, fg ? 1 : 0, 0);
+        mHandler.sendMessageDelayed(msg, 500);
+    }
+
+    private View scrollWrap(View view) {
         ScrollView scroller = new ScrollView(this);
         scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT,
                 ScrollView.LayoutParams.MATCH_PARENT));
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java
new file mode 100644
index 0000000..6330c79
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 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 android.app.Activity;
+import android.app.ActivityManager;
+import android.os.Bundle;
+
+public class DocActivity extends Activity {
+    static final String LABEL = "label";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        String label = getIntent().getStringExtra(LABEL);
+        if (label != null) {
+            setTaskDescription(new ActivityManager.TaskDescription(label));
+            setTitle(label);
+        }
+    }
+}
diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java
new file mode 100644
index 0000000..e5de559
--- /dev/null
+++ b/tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2014 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 android.app.Activity;
+import android.os.Bundle;
+
+public class SpamActivity extends Activity {
+    byte[] mBytes;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        // Chew up some RAM -- 8 megs worth.
+        mBytes = new byte[8*1024*1024];
+    }
+}