Merge "Adding method for applications to query their own tasks.  (Bug 14627210)"
diff --git a/Android.mk b/Android.mk
index 5970901..9a164c1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -68,6 +68,7 @@
 	core/java/android/app/IActivityController.aidl \
 	core/java/android/app/IActivityPendingResult.aidl \
 	core/java/android/app/IAlarmManager.aidl \
+	core/java/android/app/IAppTask.aidl \
 	core/java/android/app/IBackupAgent.aidl \
 	core/java/android/app/IInstrumentationWatcher.aidl \
 	core/java/android/app/INotificationManager.aidl \
diff --git a/api/current.txt b/api/current.txt
index 033e395..2b05f2f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3403,6 +3403,7 @@
   public class ActivityManager {
     method public boolean clearApplicationUserData();
     method public void dumpPackageState(java.io.FileDescriptor, java.lang.String);
+    method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
     method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
     method public int getLargeMemoryClass();
     method public int getLauncherLargeIconDensity();
@@ -3431,6 +3432,11 @@
     field public static final int RECENT_WITH_EXCLUDED = 1; // 0x1
   }
 
+  public static class ActivityManager.AppTask {
+    method public void finishAndRemoveTask();
+    method public android.app.ActivityManager.RecentTaskInfo getTaskInfo();
+  }
+
   public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
     ctor public ActivityManager.MemoryInfo();
     method public int describeContents();
diff --git a/core/java/android/app/ActivityManager.aidl b/core/java/android/app/ActivityManager.aidl
new file mode 100644
index 0000000..92350da
--- /dev/null
+++ b/core/java/android/app/ActivityManager.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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 android.app;
+
+parcelable ActivityManager.RecentTaskInfo;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 044727d..1d05320 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -52,6 +52,7 @@
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -474,7 +475,7 @@
     }
 
     /**
-     * Information you can set and retrieve about the current activity within Recents.
+     * Information you can set and retrieve about the current activity within the recent task list.
      */
     public static class RecentsActivityValues implements Parcelable {
         public CharSequence label;
@@ -879,7 +880,29 @@
             readFromParcel(source);
         }
     }
-    
+
+    /**
+     * Get the list of tasks associated with the calling application.
+     *
+     * @return The list of tasks associated with the application making this call.
+     * @throws SecurityException
+     */
+    public List<ActivityManager.AppTask> getAppTasks() {
+        ArrayList<AppTask> tasks = new ArrayList<AppTask>();
+        List<IAppTask> appTasks;
+        try {
+            appTasks = ActivityManagerNative.getDefault().getAppTasks();
+        } catch (RemoteException e) {
+            // System dead, we will be dead too soon!
+            return null;
+        }
+        int numAppTasks = appTasks.size();
+        for (int i = 0; i < numAppTasks; i++) {
+            tasks.add(new AppTask(appTasks.get(i)));
+        }
+        return tasks;
+    }
+
     /**
      * Return a list of the tasks that are currently running, with
      * the most recent being first and older ones after in order.  Note that
@@ -2382,4 +2405,42 @@
             return false;
         }
     }
+
+    /**
+     * The AppTask allows you to manage your own application's tasks.
+     * See {@link android.app.ActivityManager#getAppTasks()}
+     */
+    public static class AppTask {
+        private IAppTask mAppTaskImpl;
+
+        /** @hide */
+        public AppTask(IAppTask task) {
+            mAppTaskImpl = task;
+        }
+
+        /**
+         * Finishes all activities in this task and removes it from the recent tasks list.
+         */
+        public void finishAndRemoveTask() {
+            try {
+                mAppTaskImpl.finishAndRemoveTask();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Invalid AppTask", e);
+            }
+        }
+
+        /**
+         * Get the RecentTaskInfo associated with this task.
+         *
+         * @return The RecentTaskInfo for this task, or null if the task no longer exists.
+         */
+        public RecentTaskInfo getTaskInfo() {
+            try {
+                return mAppTaskImpl.getTaskInfo();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Invalid AppTask", e);
+                return null;
+            }
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 2f924d3..e704a1c 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -506,6 +506,20 @@
             return true;
         }
 
+        case GET_APP_TASKS_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            List<IAppTask> list = getAppTasks();
+            reply.writeNoException();
+            int N = list != null ? list.size() : -1;
+            reply.writeInt(N);
+            int i;
+            for (i=0; i<N; i++) {
+                IAppTask task = list.get(i);
+                reply.writeStrongBinder(task.asBinder());
+            }
+            return true;
+        }
+
         case GET_TASKS_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             int maxNum = data.readInt();
@@ -2683,6 +2697,26 @@
         reply.recycle();
         return res;
     }
+    public List<IAppTask> getAppTasks() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(GET_APP_TASKS_TRANSACTION, data, reply, 0);
+        reply.readException();
+        ArrayList<IAppTask> list = null;
+        int N = reply.readInt();
+        if (N >= 0) {
+            list = new ArrayList<IAppTask>();
+            while (N > 0) {
+                IAppTask task = IAppTask.Stub.asInterface(reply.readStrongBinder());
+                list.add(task);
+                N--;
+            }
+        }
+        data.recycle();
+        reply.recycle();
+        return list;
+    }
     public List getTasks(int maxNum, int flags) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -2698,7 +2732,7 @@
             while (N > 0) {
                 ActivityManager.RunningTaskInfo info =
                         ActivityManager.RunningTaskInfo.CREATOR
-                        .createFromParcel(reply);
+                                .createFromParcel(reply);
                 list.add(info);
                 N--;
             }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index d259b30..8753312 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -112,6 +112,7 @@
     public void activityDestroyed(IBinder token) throws RemoteException;
     public String getCallingPackage(IBinder token) throws RemoteException;
     public ComponentName getCallingActivity(IBinder token) throws RemoteException;
+    public List<IAppTask> getAppTasks() throws RemoteException;
     public List<RunningTaskInfo> getTasks(int maxNum, int flags) throws RemoteException;
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
             int flags, int userId) throws RemoteException;
@@ -741,4 +742,5 @@
     int SET_RECENTS_ACTIVITY_VALUES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+217;
     int START_VOICE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+218;
     int GET_ACTIVITY_OPTIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+219;
+    int GET_APP_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+220;
 }
diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl
new file mode 100644
index 0000000..268b4dd
--- /dev/null
+++ b/core/java/android/app/IAppTask.aidl
@@ -0,0 +1,25 @@
+/**
+ * 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 android.app;
+
+import android.app.ActivityManager;
+
+/** @hide */
+interface IAppTask {
+    void finishAndRemoveTask();
+    ActivityManager.RecentTaskInfo getTaskInfo();
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e7e2f4d..4ed68767 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -32,6 +32,7 @@
 import android.app.AppOpsManager;
 import android.app.IActivityContainer;
 import android.app.IActivityContainerCallback;
+import android.app.IAppTask;
 import android.appwidget.AppWidgetManager;
 import android.graphics.Rect;
 import android.os.BatteryStats;
@@ -7019,6 +7020,33 @@
     // =========================================================
 
     @Override
+    public List<IAppTask> getAppTasks() {
+        int callingUid = Binder.getCallingUid();
+        long ident = Binder.clearCallingIdentity();
+        synchronized(this) {
+            ArrayList<IAppTask> list = new ArrayList<IAppTask>();
+            try {
+                if (localLOGV) Slog.v(TAG, "getAppTasks");
+
+                final int N = mRecentTasks.size();
+                for (int i = 0; i < N; i++) {
+                    TaskRecord tr = mRecentTasks.get(i);
+                    // Skip tasks that are not created by the caller
+                    if (tr.creatorUid == callingUid) {
+                        ActivityManager.RecentTaskInfo taskInfo =
+                                createRecentTaskInfoFromTaskRecord(tr);
+                        AppTaskImpl taskImpl = new AppTaskImpl(taskInfo.persistentId, callingUid);
+                        list.add(taskImpl);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+            return list;
+        }
+    }
+
+    @Override
     public List<RunningTaskInfo> getTasks(int maxNum, int flags) {
         final int callingUid = Binder.getCallingUid();
         ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>();
@@ -7046,6 +7074,66 @@
         return mRecentTasks.get(0);
     }
 
+    /**
+     * Creates a new RecentTaskInfo from a TaskRecord.
+     */
+    private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) {
+        ActivityManager.RecentTaskInfo rti
+                = new ActivityManager.RecentTaskInfo();
+        rti.id = tr.numActivities > 0 ? tr.taskId : -1;
+        rti.persistentId = tr.taskId;
+        rti.baseIntent = new Intent(tr.getBaseIntent());
+        rti.origActivity = tr.origActivity;
+        rti.description = tr.lastDescription;
+        rti.stackId = tr.stack.mStackId;
+        rti.userId = tr.userId;
+
+        // Traverse upwards looking for any break between main task activities and
+        // utility activities.
+        final ArrayList<ActivityRecord> activities = tr.mActivities;
+        int activityNdx;
+        final int numActivities = activities.size();
+        for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
+             ++activityNdx) {
+            final ActivityRecord r = activities.get(activityNdx);
+            if (r.intent != null &&
+                    (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
+                            != 0) {
+                break;
+            }
+        }
+        if (activityNdx > 0) {
+            // Traverse downwards starting below break looking for set label, icon.
+            // Note that if there are activities in the task but none of them set the
+            // recent activity values, then we do not fall back to the last set
+            // values in the TaskRecord.
+            rti.activityValues = new ActivityManager.RecentsActivityValues();
+            for (--activityNdx; activityNdx >= 0; --activityNdx) {
+                final ActivityRecord r = activities.get(activityNdx);
+                if (r.activityValues != null) {
+                    if (rti.activityValues.label == null) {
+                        rti.activityValues.label = r.activityValues.label;
+                        tr.lastActivityValues.label = r.activityValues.label;
+                    }
+                    if (rti.activityValues.icon == null) {
+                        rti.activityValues.icon = r.activityValues.icon;
+                        tr.lastActivityValues.icon = r.activityValues.icon;
+                    }
+                    if (rti.activityValues.colorPrimary == 0) {
+                        rti.activityValues.colorPrimary = r.activityValues.colorPrimary;
+                        tr.lastActivityValues.colorPrimary = r.activityValues.colorPrimary;
+                    }
+                }
+            }
+        } else {
+            // If there are no activity records in this task, then we use the last
+            // resolved values
+            rti.activityValues =
+                    new ActivityManager.RecentsActivityValues(tr.lastActivityValues);
+        }
+        return rti;
+    }
+
     @Override
     public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum,
             int flags, int userId) {
@@ -7102,63 +7190,11 @@
                             continue;
                         }
                     }
-                    ActivityManager.RecentTaskInfo rti
-                            = new ActivityManager.RecentTaskInfo();
-                    rti.id = tr.numActivities > 0 ? tr.taskId : -1;
-                    rti.persistentId = tr.taskId;
-                    rti.baseIntent = new Intent(
-                            tr.intent != null ? tr.intent : tr.affinityIntent);
+
+                    ActivityManager.RecentTaskInfo rti = createRecentTaskInfoFromTaskRecord(tr);
                     if (!detailed) {
                         rti.baseIntent.replaceExtras((Bundle)null);
                     }
-                    rti.origActivity = tr.origActivity;
-                    rti.description = tr.lastDescription;
-                    rti.stackId = tr.stack.mStackId;
-                    rti.userId = tr.userId;
-
-                    // Traverse upwards looking for any break between main task activities and
-                    // utility activities.
-                    final ArrayList<ActivityRecord> activities = tr.mActivities;
-                    int activityNdx;
-                    final int numActivities = activities.size();
-                    for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
-                            ++activityNdx) {
-                        final ActivityRecord r = activities.get(activityNdx);
-                        if (r.intent != null &&
-                                (r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
-                                        != 0) {
-                            break;
-                        }
-                    }
-                    if (activityNdx > 0) {
-                        // Traverse downwards starting below break looking for set label, icon.
-                        // Note that if there are activities in the task but none of them set the
-                        // recent activity values, then we do not fall back to the last set
-                        // values in the TaskRecord.
-                        rti.activityValues = new ActivityManager.RecentsActivityValues();
-                        for (--activityNdx; activityNdx >= 0; --activityNdx) {
-                            final ActivityRecord r = activities.get(activityNdx);
-                            if (r.activityValues != null) {
-                                if (rti.activityValues.label == null) {
-                                    rti.activityValues.label = r.activityValues.label;
-                                    tr.lastActivityValues.label = r.activityValues.label;
-                                }
-                                if (rti.activityValues.icon == null) {
-                                    rti.activityValues.icon = r.activityValues.icon;
-                                    tr.lastActivityValues.icon = r.activityValues.icon;
-                                }
-                                if (rti.activityValues.colorPrimary == 0) {
-                                    rti.activityValues.colorPrimary = r.activityValues.colorPrimary;
-                                    tr.lastActivityValues.colorPrimary = r.activityValues.colorPrimary;
-                                }
-                            }
-                        }
-                    } else {
-                        // If there are no activity records in this task, then we use the last
-                        // resolved values
-                        rti.activityValues =
-                                new ActivityManager.RecentsActivityValues(tr.lastActivityValues);
-                    }
 
                     if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) {
                         // Check whether this activity is currently available.
@@ -17174,4 +17210,69 @@
             ActivityManagerService.this.wakingUp();
         }
     }
+
+    /**
+     * An implementation of IAppTask, that allows an app to manage its own tasks via
+     * {@link android.app.ActivityManager#AppTask}.  We keep track of the callingUid to ensure that
+     * only the process that calls getAppTasks() can call the AppTask methods.
+     */
+    class AppTaskImpl extends IAppTask.Stub {
+        private int mTaskId;
+        private int mCallingUid;
+
+        public AppTaskImpl(int taskId, int callingUid) {
+            mTaskId = taskId;
+            mCallingUid = callingUid;
+        }
+
+        @Override
+        public void finishAndRemoveTask() {
+            // Ensure that we are called from the same process that created this AppTask
+            if (mCallingUid != Binder.getCallingUid()) {
+                Slog.w(TAG, "finishAndRemoveTask: caller " + mCallingUid
+                        + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+                return;
+            }
+
+            synchronized (ActivityManagerService.this) {
+                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);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+            }
+        }
+
+        @Override
+        public ActivityManager.RecentTaskInfo getTaskInfo() {
+            // Ensure that we are called from the same process that created this AppTask
+            if (mCallingUid != Binder.getCallingUid()) {
+                Slog.w(TAG, "finishAndRemoveTask: caller " + mCallingUid
+                        + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+                return null;
+            }
+
+            synchronized (ActivityManagerService.this) {
+                long origId = Binder.clearCallingIdentity();
+                try {
+                    TaskRecord tr = recentTaskForIdLocked(mTaskId);
+                    if (tr != null) {
+                        return createRecentTaskInfoFromTaskRecord(tr);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(origId);
+                }
+                return null;
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 862932c..be884e7 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -154,6 +154,11 @@
         }
     }
 
+    /** Returns the intent for the root activity for this task */
+    Intent getBaseIntent() {
+        return intent != null ? intent : affinityIntent;
+    }
+
     /** Returns the first non-finishing activity from the root. */
     ActivityRecord getRootActivity() {
         for (int i = 0; i < mActivities.size(); i++) {