Adding method for applications to query their own tasks. (Bug 14627210)
Change-Id: I33299bf59784849171b19af4a5be2ab7665581c5
diff --git a/Android.mk b/Android.mk
index 5e634c1..7ce7521 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 ffcadfc..b802c3b 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++) {