Merge "Piping through ability for an Activity to remove its own task. (Bug 13735914)"
diff --git a/api/current.txt b/api/current.txt
index 08eee23..ef6aed7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3175,6 +3175,7 @@
     method public void finishActivity(int);
     method public void finishActivityFromChild(android.app.Activity, int);
     method public void finishAffinity();
+    method public void finishAndRemoveTask();
     method public void finishFromChild(android.app.Activity);
     method public void finishWithTransition();
     method public android.app.ActionBar getActionBar();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5a06e3..599a608 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4314,11 +4314,10 @@
     }
 
     /**
-     * Call this when your activity is done and should be closed.  The
-     * ActivityResult is propagated back to whoever launched you via
-     * onActivityResult().
+     * Finishes the current activity and specifies whether to remove the task associated with this
+     * activity.
      */
-    public void finish() {
+    private void finish(boolean finishTask) {
         if (mParent == null) {
             int resultCode;
             Intent resultData;
@@ -4332,7 +4331,7 @@
                     resultData.prepareToLeaveProcess();
                 }
                 if (ActivityManagerNative.getDefault()
-                    .finishActivity(mToken, resultCode, resultData)) {
+                        .finishActivity(mToken, resultCode, resultData, finishTask)) {
                     mFinished = true;
                 }
             } catch (RemoteException e) {
@@ -4344,6 +4343,15 @@
     }
 
     /**
+     * Call this when your activity is done and should be closed.  The
+     * ActivityResult is propagated back to whoever launched you via
+     * onActivityResult().
+     */
+    public void finish() {
+        finish(false);
+    }
+
+    /**
      * Finish this activity as well as all activities immediately below it
      * in the current task that have the same affinity.  This is typically
      * used when an application can be launched on to another task (such as
@@ -4442,6 +4450,14 @@
     }
 
     /**
+     * Call this when your activity is done and should be closed and the task should be completely
+     * removed as a part of finishing the Activity.
+     */
+    public void finishAndRemoveTask() {
+        finish(true);
+    }
+
+    /**
      * 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/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 44c74d8..10831f2 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -263,7 +263,8 @@
             if (data.readInt() != 0) {
                 resultData = Intent.CREATOR.createFromParcel(data);
             }
-            boolean res = finishActivity(token, resultCode, resultData);
+            boolean finishTask = (data.readInt() != 0);
+            boolean res = finishActivity(token, resultCode, resultData, finishTask);
             reply.writeNoException();
             reply.writeInt(res ? 1 : 0);
             return true;
@@ -2342,7 +2343,7 @@
         data.recycle();
         return result != 0;
     }
-    public boolean finishActivity(IBinder token, int resultCode, Intent resultData)
+    public boolean finishActivity(IBinder token, int resultCode, Intent resultData, boolean finishTask)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -2355,6 +2356,7 @@
         } else {
             data.writeInt(0);
         }
+        data.writeInt(finishTask ? 1 : 0);
         mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0);
         reply.readException();
         boolean res = reply.readInt() != 0;
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 965f815..88eae7f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2363,7 +2363,7 @@
             // manager to stop us.
             try {
                 ActivityManagerNative.getDefault()
-                    .finishActivity(r.token, Activity.RESULT_CANCELED, null);
+                    .finishActivity(r.token, Activity.RESULT_CANCELED, null, false);
             } catch (RemoteException ex) {
                 // Ignore
             }
@@ -2984,7 +2984,7 @@
             // just end this activity.
             try {
                 ActivityManagerNative.getDefault()
-                    .finishActivity(token, Activity.RESULT_CANCELED, null);
+                    .finishActivity(token, Activity.RESULT_CANCELED, null, false);
             } catch (RemoteException ex) {
             }
         }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index bfbd339..52003f1 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -79,7 +79,7 @@
             int flagsMask, int flagsValues, Bundle options) throws RemoteException;
     public boolean startNextMatchingActivity(IBinder callingActivity,
             Intent intent, Bundle options) throws RemoteException;
-    public boolean finishActivity(IBinder token, int code, Intent data)
+    public boolean finishActivity(IBinder token, int code, Intent data, boolean finishTask)
             throws RemoteException;
     public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
     public boolean finishActivityAffinity(IBinder token) throws RemoteException;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 51296c1..8fa076b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3453,11 +3453,14 @@
      * @param token The Binder token referencing the Activity we want to finish.
      * @param resultCode Result code, if any, from this Activity.
      * @param resultData Result data (Intent), if any, from this Activity.
+     * @param finishTask Whether to finish the task associated with this Activity.  Only applies to
+     *            the root Activity in the task.
      *
      * @return Returns true if the activity successfully finished, or false if it is still running.
      */
     @Override
-    public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
+    public final boolean finishActivity(IBinder token, int resultCode, Intent resultData,
+            boolean finishTask) {
         // Refuse possible leaked file descriptors
         if (resultData != null && resultData.hasFileDescriptors() == true) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -3468,6 +3471,9 @@
             if (r == null) {
                 return true;
             }
+            // Keep track of the root activity of the task before we finish it
+            TaskRecord tr = r.task;
+            ActivityRecord rootR = tr.getRootActivity();
             if (mController != null) {
                 // Find the first activity that is not finishing.
                 ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0);
@@ -3487,10 +3493,21 @@
                 }
             }
             final long origId = Binder.clearCallingIdentity();
-            boolean res = r.task.stack.requestFinishActivityLocked(token, resultCode,
-                    resultData, "app-request", true);
-            Binder.restoreCallingIdentity(origId);
-            return res;
+            try {
+                boolean res;
+                if (finishTask && r == rootR) {
+                    // If requested, remove the task that is associated to this activity only if it
+                    // was the root activity in the task.  The result code and data is ignored because
+                    // we don't support returning them across task boundaries.
+                    res = removeTaskByIdLocked(tr.taskId, 0);
+                } else {
+                    res = tr.stack.requestFinishActivityLocked(token, resultCode,
+                            resultData, "app-request", true);
+                }
+                return res;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
         }
     }
 
@@ -7144,6 +7161,24 @@
         }
     }
 
+    /**
+     * Removes the task with the specified task id.
+     *
+     * @param taskId Identifier of the task to be removed.
+     * @param flags Additional operational flags.  May be 0 or
+     * {@link ActivityManager#REMOVE_TASK_KILL_PROCESS}.
+     * @return Returns true if the given task was found and removed.
+     */
+    private boolean removeTaskByIdLocked(int taskId, int flags) {
+        TaskRecord tr = recentTaskForIdLocked(taskId);
+        if (tr != null) {
+            tr.removeTaskActivitiesLocked(-1, false);
+            cleanUpRemovedTaskLocked(tr, flags);
+            return true;
+        }
+        return false;
+    }
+
     @Override
     public boolean removeTask(int taskId, int flags) {
         synchronized (this) {
@@ -7151,29 +7186,11 @@
                     "removeTask()");
             long ident = Binder.clearCallingIdentity();
             try {
-                TaskRecord tr = recentTaskForIdLocked(taskId);
-                if (tr != null) {
-                    ActivityRecord r = tr.removeTaskActivitiesLocked(-1, false);
-                    if (r != null) {
-                        cleanUpRemovedTaskLocked(tr, flags);
-                        return true;
-                    }
-                    if (tr.mActivities.size() == 0) {
-                        // Caller is just removing a recent task that is
-                        // not actively running.  That is easy!
-                        cleanUpRemovedTaskLocked(tr, flags);
-                        return true;
-                    }
-                    Slog.w(TAG, "removeTask: task " + taskId
-                            + " does not have activities to remove, "
-                            + " but numActivities=" + tr.numActivities
-                            + ": " + tr);
-                }
+                return removeTaskByIdLocked(taskId, flags);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
-        return false;
     }
     
     /**
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 3a43521..80a219dd 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -139,6 +139,18 @@
         }
     }
 
+    /** Returns the first non-finishing activity from the root. */
+    ActivityRecord getRootActivity() {
+        for (int i = 0; i < mActivities.size(); i++) {
+            final ActivityRecord r = mActivities.get(i);
+            if (r.finishing) {
+                continue;
+            }
+            return r;
+        }
+        return null;
+    }
+
     ActivityRecord getTopActivity() {
         for (int i = mActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = mActivities.get(i);
@@ -305,7 +317,7 @@
     }
 
     public ActivityManager.TaskThumbnails getTaskThumbnailsLocked() {
-        TaskAccessInfo info = getTaskAccessInfoLocked(true);
+        TaskAccessInfo info = getTaskAccessInfoLocked();
         final ActivityRecord resumedActivity = stack.mResumedActivity;
         if (resumedActivity != null && resumedActivity.thumbHolder == this) {
             info.mainThumbnail = stack.screenshotActivities(resumedActivity);
@@ -325,7 +337,7 @@
         }
         // Return the information about the task, to figure out the top
         // thumbnail to return.
-        TaskAccessInfo info = getTaskAccessInfoLocked(true);
+        TaskAccessInfo info = getTaskAccessInfoLocked();
         if (info.numSubThumbbails <= 0) {
             return info.mainThumbnail != null ? info.mainThumbnail : lastThumbnail;
         }
@@ -334,7 +346,7 @@
 
     public ActivityRecord removeTaskActivitiesLocked(int subTaskIndex,
             boolean taskRequired) {
-        TaskAccessInfo info = getTaskAccessInfoLocked(false);
+        TaskAccessInfo info = getTaskAccessInfoLocked();
         if (info.root == null) {
             if (taskRequired) {
                 Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId);
@@ -369,7 +381,7 @@
         return mTaskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
     }
 
-    public TaskAccessInfo getTaskAccessInfoLocked(boolean inclThumbs) {
+    public TaskAccessInfo getTaskAccessInfoLocked() {
         final TaskAccessInfo thumbs = new TaskAccessInfo();
         // How many different sub-thumbnails?
         final int NA = mActivities.size();