Add new Activity.finishAffinity() method.

It's cool!

Change-Id: I9fdcd9535b7f1ca2b311d3a1b23e5d058977b095
diff --git a/api/current.txt b/api/current.txt
index 46e3aa0..be8e139 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2601,6 +2601,7 @@
     method public void finish();
     method public void finishActivity(int);
     method public void finishActivityFromChild(android.app.Activity, int);
+    method public void finishAffinity();
     method public void finishFromChild(android.app.Activity);
     method public android.app.ActionBar getActionBar();
     method public final android.app.Application getApplication();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 35bc7ff..4add7f4 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -4093,6 +4093,36 @@
     }
 
     /**
+     * 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
+     * from an ACTION_VIEW of a content type it understands) and the user
+     * has used the up navigation to switch out of the current task and in
+     * to its own task.  In this case, if the user has navigated down into
+     * any other activities of the second application, all of those should
+     * be removed from the original task as part of the task switch.
+     *
+     * <p>Note that this finish does <em>not</em> allow you to deliver results
+     * to the previous activity, and an exception will be thrown if you are trying
+     * to do so.</p>
+     */
+    public void finishAffinity() {
+        if (mParent != null) {
+            throw new IllegalStateException("Can not be called from an embedded activity");
+        }
+        if (mResultCode != RESULT_CANCELED || mResultData != null) {
+            throw new IllegalStateException("Can not be called to deliver a result");
+        }
+        try {
+            if (ActivityManagerNative.getDefault().finishActivityAffinity(mToken)) {
+                mFinished = true;
+            }
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
+    /**
      * This is called when a child activity of this one calls its 
      * {@link #finish} method.  The default implementation simply calls
      * finish() on this activity (the parent), finishing the entire group.
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 7e1589f..2f2918d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -218,7 +218,7 @@
             reply.writeInt(result ? 1 : 0);
             return true;
         }
-        
+
         case FINISH_ACTIVITY_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
@@ -243,6 +243,15 @@
             return true;
         }
 
+        case FINISH_ACTIVITY_AFFINITY_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder token = data.readStrongBinder();
+            boolean res = finishActivityAffinity(token);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
         case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
@@ -1866,6 +1875,18 @@
         data.recycle();
         reply.recycle();
     }
+    public boolean finishActivityAffinity(IBinder token) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(token);
+        mRemote.transact(FINISH_ACTIVITY_AFFINITY_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
     public boolean willActivityBeVisible(IBinder token) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 3fc2280..a2c7fa4 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -72,6 +72,7 @@
     public boolean finishActivity(IBinder token, int code, Intent data)
             throws RemoteException;
     public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
+    public boolean finishActivityAffinity(IBinder token) throws RemoteException;
     public boolean willActivityBeVisible(IBinder token) throws RemoteException;
     public Intent registerReceiver(IApplicationThread caller, String callerPackage,
             IIntentReceiver receiver, IntentFilter filter,
@@ -590,4 +591,5 @@
     int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145;
     int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146;
     int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147;
+    int FINISH_ACTIVITY_AFFINITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+148;
 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6c99cdb..429c3c4 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -2697,29 +2697,21 @@
     public final void finishSubActivity(IBinder token, String resultWho,
             int requestCode) {
         synchronized(this) {
-            ActivityRecord self = mMainStack.isInStackLocked(token);
-            if (self == null) {
-                return;
-            }
-
             final long origId = Binder.clearCallingIdentity();
-
-            int i;
-            for (i=mMainStack.mHistory.size()-1; i>=0; i--) {
-                ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i);
-                if (r.resultTo == self && r.requestCode == requestCode) {
-                    if ((r.resultWho == null && resultWho == null) ||
-                        (r.resultWho != null && r.resultWho.equals(resultWho))) {
-                        mMainStack.finishActivityLocked(r, i,
-                                Activity.RESULT_CANCELED, null, "request-sub");
-                    }
-                }
-            }
-
+            mMainStack.finishSubActivityLocked(token, resultWho, requestCode);
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    public boolean finishActivityAffinity(IBinder token) {
+        synchronized(this) {
+            final long origId = Binder.clearCallingIdentity();
+            boolean res = mMainStack.finishActivityAffinityLocked(token);
+            Binder.restoreCallingIdentity(origId);
+            return res;
+        }
+    }
+
     public boolean willActivityBeVisible(IBinder token) {
         synchronized(this) {
             int i;
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index c8e015b..25fae83 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -3497,6 +3497,51 @@
         return true;
     }
 
+    final void finishSubActivityLocked(IBinder token, String resultWho, int requestCode) {
+        ActivityRecord self = isInStackLocked(token);
+        if (self == null) {
+            return;
+        }
+
+        int i;
+        for (i=mHistory.size()-1; i>=0; i--) {
+            ActivityRecord r = (ActivityRecord)mHistory.get(i);
+            if (r.resultTo == self && r.requestCode == requestCode) {
+                if ((r.resultWho == null && resultWho == null) ||
+                    (r.resultWho != null && r.resultWho.equals(resultWho))) {
+                    finishActivityLocked(r, i,
+                            Activity.RESULT_CANCELED, null, "request-sub");
+                }
+            }
+        }
+    }
+
+    final boolean finishActivityAffinityLocked(IBinder token) {
+        int index = indexOfTokenLocked(token);
+        if (DEBUG_RESULTS) Slog.v(
+                TAG, "Finishing activity affinity @" + index + ": token=" + token);
+        if (index < 0) {
+            return false;
+        }
+        ActivityRecord r = mHistory.get(index);
+
+        while (index > 0) {
+            ActivityRecord cur = mHistory.get(index);
+            if (cur.task != r.task) {
+                break;
+            }
+            if (cur.taskAffinity == null && r.taskAffinity != null) {
+                break;
+            }
+            if (cur.taskAffinity != null && !cur.taskAffinity.equals(r.taskAffinity)) {
+                break;
+            }
+            finishActivityLocked(cur, index, Activity.RESULT_CANCELED, null, "request-affinity");
+            index--;
+        }
+        return true;
+    }
+
     final void finishActivityResultsLocked(ActivityRecord r, int resultCode, Intent resultData) {
         // send the result
         ActivityRecord resultTo = r.resultTo;