Fix issue #17038762: Add API to add entries to the recents list
New API Added to ActivityManager for adding an entry. See docs
there.
Repercussions:
- I hit a bug in system UI where if the thumbnail has alpha, it tries
to modify it, but thumbnails are loading immutable so crashes. Fixed
this by loading the bitmaps to be mutable.
- Improved dump output of recents; there was a lot of stuff missing.
Also split the recents dump output from the rest of the activity
output, since it can be really large.
- Added tests to the lovely ActivityTest app.
Bonus: new method on AppTask to control the exclude from recents flag.
Change-Id: I01e543db4d15320ee1701e95872fef73c116526c
diff --git a/api/current.txt b/api/current.txt
index 5b8ba4d..29c9ffb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3593,8 +3593,11 @@
}
public class ActivityManager {
+ method public int addAppTask(android.app.Activity, android.content.Intent, android.app.ActivityManager.TaskDescription, android.graphics.Bitmap);
method public boolean clearApplicationUserData();
method public void dumpPackageState(java.io.FileDescriptor, java.lang.String);
+ method public int getAppTaskThumbnailHeight();
+ method public int getAppTaskThumbnailWidth();
method public java.util.List<android.app.ActivityManager.AppTask> getAppTasks();
method public android.content.pm.ConfigurationInfo getDeviceConfigurationInfo();
method public int getLargeMemoryClass();
@@ -3628,6 +3631,7 @@
public static class ActivityManager.AppTask {
method public void finishAndRemoveTask();
method public android.app.ActivityManager.RecentTaskInfo getTaskInfo();
+ method public void setExcludeFromRecents(boolean);
}
public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4b022ff..b86621f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -16,6 +16,11 @@
package android.app;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Point;
import android.os.BatteryStats;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -47,16 +52,13 @@
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.Slog;
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;
/**
* Interact with the overall activities running in the system.
@@ -297,6 +299,8 @@
/** @hide Process is being cached for later use and is empty. */
public static final int PROCESS_STATE_CACHED_EMPTY = 13;
+ Point mAppTaskThumbnailSize;
+
/*package*/ ActivityManager(Context context, Handler handler) {
mContext = context;
mHandler = handler;
@@ -994,6 +998,103 @@
}
/**
+ * Return the current design width for {@link AppTask} thumbnails, for use
+ * with {@link #addAppTask}.
+ */
+ public int getAppTaskThumbnailWidth() {
+ synchronized (this) {
+ ensureAppTaskThumbnailSizeLocked();
+ return mAppTaskThumbnailSize.x;
+ }
+ }
+
+ /**
+ * Return the current design height for {@link AppTask} thumbnails, for use
+ * with {@link #addAppTask}.
+ */
+ public int getAppTaskThumbnailHeight() {
+ synchronized (this) {
+ ensureAppTaskThumbnailSizeLocked();
+ return mAppTaskThumbnailSize.y;
+ }
+ }
+
+ private void ensureAppTaskThumbnailSizeLocked() {
+ if (mAppTaskThumbnailSize == null) {
+ try {
+ mAppTaskThumbnailSize = ActivityManagerNative.getDefault().getAppTaskThumbnailSize();
+ } catch (RemoteException e) {
+ throw new IllegalStateException("System dead?", e);
+ }
+ }
+ }
+
+ /**
+ * Add a new {@link AppTask} for the calling application. This will create a new
+ * recents entry that is added to the <b>end</b> of all existing recents.
+ *
+ * @param activity The activity that is adding the entry. This is used to help determine
+ * the context that the new recents entry will be in.
+ * @param intent The Intent that describes the recents entry. This is the same Intent that
+ * you would have used to launch the activity for it. In generally you will want to set
+ * both {@link Intent#FLAG_ACTIVITY_NEW_DOCUMENT} and
+ * {@link Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}; the latter is required since this recents
+ * entry will exist without an activity, so it doesn't make sense to not retain it when
+ * its activity disappears. The given Intent here also must have an explicit ComponentName
+ * set on it.
+ * @param description Optional additional description information.
+ * @param thumbnail Thumbnail to use for the recents entry. Should be the size given by
+ * {@link #getAppTaskThumbnailWidth()} and {@link #getAppTaskThumbnailHeight()}. If the
+ * bitmap is not that exact size, it will be recreated in your process, probably in a way
+ * you don't like, before the recents entry is added.
+ *
+ * @return Returns the task id of the newly added app task, or -1 if the add failed. The
+ * most likely cause of failure is that there is no more room for more tasks for your app.
+ */
+ public int addAppTask(@NonNull Activity activity, @NonNull Intent intent,
+ @Nullable TaskDescription description, @NonNull Bitmap thumbnail) {
+ Point size;
+ synchronized (this) {
+ ensureAppTaskThumbnailSizeLocked();
+ size = mAppTaskThumbnailSize;
+ }
+ final int tw = thumbnail.getWidth();
+ final int th = thumbnail.getHeight();
+ if (tw != size.x || th != size.y) {
+ Bitmap bm = Bitmap.createBitmap(size.x, size.y, thumbnail.getConfig());
+
+ // Use ScaleType.CENTER_CROP, except we leave the top edge at the top.
+ float scale;
+ float dx = 0, dy = 0;
+ if (tw * size.x > size.y * th) {
+ scale = (float) size.x / (float) th;
+ dx = (size.y - tw * scale) * 0.5f;
+ } else {
+ scale = (float) size.y / (float) tw;
+ dy = (size.x - th * scale) * 0.5f;
+ }
+ Matrix matrix = new Matrix();
+ matrix.setScale(scale, scale);
+ matrix.postTranslate((int) (dx + 0.5f), 0);
+
+ Canvas canvas = new Canvas(bm);
+ canvas.drawBitmap(thumbnail, matrix, null);
+ canvas.setBitmap(null);
+
+ thumbnail = bm;
+ }
+ if (description == null) {
+ description = new TaskDescription();
+ }
+ try {
+ return ActivityManagerNative.getDefault().addAppTask(activity.getActivityToken(),
+ intent, description, thumbnail);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("System dead?", e);
+ }
+ }
+
+ /**
* Return a list of the tasks that are currently running, with
* the most recent being first and older ones after in order. Note that
* "running" does not mean any of the task's code is currently loaded or
@@ -2514,5 +2615,20 @@
return null;
}
}
+
+ /**
+ * Modify the {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag in the root
+ * Intent of this AppTask.
+ *
+ * @param exclude If true, {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} will
+ * be set; otherwise, it will be cleared.
+ */
+ public void setExcludeFromRecents(boolean exclude) {
+ try {
+ mAppTaskImpl.setExcludeFromRecents(exclude);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Invalid AppTask", e);
+ }
+ }
}
}
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1cb1047..3dafa4b 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -30,6 +30,8 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Binder;
@@ -564,6 +566,27 @@
return true;
}
+ case ADD_APP_TASK_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder activityToken = data.readStrongBinder();
+ Intent intent = Intent.CREATOR.createFromParcel(data);
+ ActivityManager.TaskDescription descr
+ = ActivityManager.TaskDescription.CREATOR.createFromParcel(data);
+ Bitmap thumbnail = Bitmap.CREATOR.createFromParcel(data);
+ int res = addAppTask(activityToken, intent, descr, thumbnail);
+ reply.writeNoException();
+ reply.writeInt(res);
+ return true;
+ }
+
+ case GET_APP_TASK_THUMBNAIL_SIZE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ Point size = getAppTaskThumbnailSize();
+ reply.writeNoException();
+ size.writeToParcel(reply, 0);
+ return true;
+ }
+
case GET_TASKS_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int maxNum = data.readInt();
@@ -2877,6 +2900,33 @@
reply.recycle();
return list;
}
+ public int addAppTask(IBinder activityToken, Intent intent,
+ ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(activityToken);
+ intent.writeToParcel(data, 0);
+ description.writeToParcel(data, 0);
+ thumbnail.writeToParcel(data, 0);
+ mRemote.transact(ADD_APP_TASK_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int res = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+ public Point getAppTaskThumbnailSize() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_APP_TASK_THUMBNAIL_SIZE_TRANSACTION, data, reply, 0);
+ reply.readException();
+ Point size = Point.CREATOR.createFromParcel(reply);
+ data.recycle();
+ reply.recycle();
+ return size;
+ }
public List getTasks(int maxNum, int flags) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index b2812e3..9342ae5 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -749,10 +749,10 @@
putCachedIcon(name, dr);
return dr;
} catch (NameNotFoundException e) {
- Log.w("PackageManager", "Failure retrieving resources for"
+ Log.w("PackageManager", "Failure retrieving resources for "
+ appInfo.packageName);
} catch (Resources.NotFoundException e) {
- Log.w("PackageManager", "Failure retrieving resources for"
+ Log.w("PackageManager", "Failure retrieving resources for "
+ appInfo.packageName + ": " + e.getMessage());
} catch (RuntimeException e) {
// If an exception was thrown, fall through to return
@@ -1100,7 +1100,7 @@
putCachedString(name, text);
return text;
} catch (NameNotFoundException e) {
- Log.w("PackageManager", "Failure retrieving resources for"
+ Log.w("PackageManager", "Failure retrieving resources for "
+ appInfo.packageName);
} catch (RuntimeException e) {
// If an exception was thrown, fall through to return
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 8e21899..70514d8 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -36,6 +36,7 @@
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
@@ -119,6 +120,9 @@
public String getCallingPackage(IBinder token) throws RemoteException;
public ComponentName getCallingActivity(IBinder token) throws RemoteException;
public List<IAppTask> getAppTasks() throws RemoteException;
+ public int addAppTask(IBinder activityToken, Intent intent,
+ ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException;
+ public Point getAppTaskThumbnailSize() 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;
@@ -765,4 +769,6 @@
int NOTIFY_ENTER_ANIMATION_COMPLETE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+230;
int KEYGUARD_WAITING_FOR_ACTIVITY_DRAWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+231;
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;
}
diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl
index 268b4dd..4e38c36 100644
--- a/core/java/android/app/IAppTask.aidl
+++ b/core/java/android/app/IAppTask.aidl
@@ -22,4 +22,5 @@
interface IAppTask {
void finishAndRemoveTask();
ActivityManager.RecentTaskInfo getTaskInfo();
+ void setExcludeFromRecents(boolean exclude);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index fb77751..5390daf 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -71,6 +71,8 @@
public class SystemServicesProxy {
final static String TAG = "SystemServicesProxy";
+ final static BitmapFactory.Options sBitmapOptions;
+
ActivityManager mAm;
IActivityManager mIam;
AppWidgetManager mAwm;
@@ -89,6 +91,11 @@
Paint mBgProtectionPaint;
Canvas mBgProtectionCanvas;
+ static {
+ sBitmapOptions = new BitmapFactory.Options();
+ sBitmapOptions.inMutable = true;
+ }
+
/** Private constructor */
public SystemServicesProxy(Context context) {
mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
@@ -258,7 +265,8 @@
Bitmap thumbnail = taskThumbnail.mainThumbnail;
ParcelFileDescriptor descriptor = taskThumbnail.thumbnailFileDescriptor;
if (thumbnail == null && descriptor != null) {
- thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor());
+ thumbnail = BitmapFactory.decodeFileDescriptor(descriptor.getFileDescriptor(),
+ null, sBitmapOptions);
}
if (descriptor != null) {
try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b1d84f58..e8b1d03 100755
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -40,6 +40,9 @@
import android.app.usage.UsageEvents;
import android.app.usage.UsageStatsManagerInternal;
import android.appwidget.AppWidgetManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.BatteryStats;
import android.os.PersistableBundle;
@@ -261,6 +264,7 @@
static final boolean DEBUG_VISBILITY = localLOGV || false;
static final boolean DEBUG_PSS = localLOGV || false;
static final boolean DEBUG_LOCKSCREEN = localLOGV || false;
+ static final boolean DEBUG_RECENTS = localLOGV || false;
static final boolean VALIDATE_TOKENS = false;
static final boolean SHOW_ACTIVITY_START_TIME = true;
@@ -410,6 +414,21 @@
ArrayList<TaskRecord> mRecentTasks;
ArraySet<TaskRecord> mTmpRecents = new ArraySet<TaskRecord>();
+ /**
+ * For addAppTask: cached of the last activity component that was added.
+ */
+ ComponentName mLastAddedTaskComponent;
+
+ /**
+ * For addAppTask: cached of the last activity uid that was added.
+ */
+ int mLastAddedTaskUid;
+
+ /**
+ * For addAppTask: cached of the last ActivityInfo that was added.
+ */
+ ActivityInfo mLastAddedTaskActivity;
+
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
public boolean haveResult = false;
@@ -1181,6 +1200,9 @@
/** Flag whether the device has a recents UI */
final boolean mHasRecents;
+ final int mThumbnailWidth;
+ final int mThumbnailHeight;
+
final ServiceThread mHandlerThread;
final MainHandler mHandler;
@@ -2229,8 +2251,10 @@
mConfigurationSeq = mConfiguration.seq = 1;
mProcessCpuTracker.init();
- mHasRecents = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_hasRecents);
+ final Resources res = mContext.getResources();
+ mHasRecents = res.getBoolean(com.android.internal.R.bool.config_hasRecents);
+ mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
+ mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
@@ -3781,7 +3805,7 @@
}
final void addRecentTaskLocked(TaskRecord task) {
- int N = mRecentTasks.size();
+ final int N = mRecentTasks.size();
// Quick case: check if the top-most recent task is the same.
if (N > 0 && mRecentTasks.get(0) == task) {
return;
@@ -3790,10 +3814,25 @@
if (task.voiceSession != null) {
return;
}
- // Remove any existing entries that are the same kind of task.
+
+ trimRecentsForTask(task, true);
+
+ if (N >= MAX_RECENT_TASKS) {
+ final TaskRecord tr = mRecentTasks.remove(N - 1);
+ tr.disposeThumbnail();
+ tr.closeRecentsChain();
+ }
+ mRecentTasks.add(0, task);
+ }
+
+ /**
+ * If needed, remove oldest existing entries in recents that are for the same kind
+ * of task as the given one.
+ */
+ int trimRecentsForTask(TaskRecord task, boolean doTrim) {
+ int N = mRecentTasks.size();
final Intent intent = task.intent;
final boolean document = intent != null && intent.isDocument();
- final ComponentName comp = intent.getComponent();
int maxRecents = task.maxRecents - 1;
for (int i=0; i<N; i++) {
@@ -3825,6 +3864,12 @@
}
}
+ if (!doTrim) {
+ // If the caller is not actually asking for a trim, just tell them we reached
+ // a point where the trim would happen.
+ return i;
+ }
+
// Either task and tr are the same or, their affinities match or their intents match
// and neither of them is a document, or they are documents using the same activity
// and their maxRecents has been reached.
@@ -3842,12 +3887,8 @@
}
notifyTaskPersisterLocked(tr, false);
}
- if (N >= MAX_RECENT_TASKS) {
- final TaskRecord tr = mRecentTasks.remove(N - 1);
- tr.disposeThumbnail();
- tr.closeRecentsChain();
- }
- mRecentTasks.add(0, task);
+
+ return -1;
}
@Override
@@ -7639,7 +7680,10 @@
for (int i=0; i<N && maxNum > 0; i++) {
TaskRecord tr = mRecentTasks.get(i);
// Only add calling user or related users recent tasks
- if (!includedUsers.contains(Integer.valueOf(tr.userId))) continue;
+ if (!includedUsers.contains(Integer.valueOf(tr.userId))) {
+ if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, not user: " + tr);
+ continue;
+ }
// Return the entry if desired by the caller. We always return
// the first entry, because callers always expect this to be the
@@ -7656,11 +7700,14 @@
// If the caller doesn't have the GET_TASKS permission, then only
// allow them to see a small subset of tasks -- their own and home.
if (!tr.isHomeTask() && tr.creatorUid != callingUid) {
+ if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, not allowed: " + tr);
continue;
}
}
if (tr.autoRemoveRecents && tr.getTopActivity() == null) {
// Don't include auto remove tasks that are finished or finishing.
+ if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, auto-remove without activity: "
+ + tr);
continue;
}
@@ -7675,11 +7722,15 @@
if (rti.origActivity != null) {
if (pm.getActivityInfo(rti.origActivity, 0, userId)
== null) {
+ if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, unavail orig act: "
+ + tr);
continue;
}
} else if (rti.baseIntent != null) {
if (pm.queryIntentActivities(rti.baseIntent,
null, 0, userId) == null) {
+ if (DEBUG_RECENTS) Slog.d(TAG, "Skipping, unavail intent: "
+ + tr);
continue;
}
}
@@ -7721,6 +7772,97 @@
}
@Override
+ public int addAppTask(IBinder activityToken, Intent intent,
+ ActivityManager.TaskDescription description, Bitmap thumbnail) throws RemoteException {
+ final int callingUid = Binder.getCallingUid();
+ final long callingIdent = Binder.clearCallingIdentity();
+
+ try {
+ synchronized (this) {
+ ActivityRecord r = ActivityRecord.isInStackLocked(activityToken);
+ if (r == null) {
+ throw new IllegalArgumentException("Activity does not exist; token="
+ + activityToken);
+ }
+ ComponentName comp = intent.getComponent();
+ if (comp == null) {
+ throw new IllegalArgumentException("Intent " + intent
+ + " must specify explicit component");
+ }
+ if (thumbnail.getWidth() != mThumbnailWidth
+ || thumbnail.getHeight() != mThumbnailHeight) {
+ throw new IllegalArgumentException("Bad thumbnail size: got "
+ + thumbnail.getWidth() + "x" + thumbnail.getHeight() + ", require "
+ + mThumbnailWidth + "x" + mThumbnailHeight);
+ }
+ if (intent.getSelector() != null) {
+ intent.setSelector(null);
+ }
+ if (intent.getSourceBounds() != null) {
+ intent.setSourceBounds(null);
+ }
+ if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0) {
+ if ((intent.getFlags()&Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS) == 0) {
+ // The caller has added this as an auto-remove task... that makes no
+ // sense, so turn off auto-remove.
+ intent.addFlags(Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+ }
+ } else if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ // Must be a new task.
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ }
+ if (!comp.equals(mLastAddedTaskComponent) || callingUid != mLastAddedTaskUid) {
+ mLastAddedTaskActivity = null;
+ }
+ ActivityInfo ainfo = mLastAddedTaskActivity;
+ if (ainfo == null) {
+ ainfo = mLastAddedTaskActivity = AppGlobals.getPackageManager().getActivityInfo(
+ comp, 0, UserHandle.getUserId(callingUid));
+ if (ainfo.applicationInfo.uid != callingUid) {
+ throw new SecurityException(
+ "Can't add task for another application: target uid="
+ + ainfo.applicationInfo.uid + ", calling uid=" + callingUid);
+ }
+ }
+
+ TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo,
+ intent, description);
+
+ int trimIdx = trimRecentsForTask(task, false);
+ if (trimIdx >= 0) {
+ // If this would have caused a trim, then we'll abort because that
+ // means it would be added at the end of the list but then just removed.
+ return -1;
+ }
+
+ final int N = mRecentTasks.size();
+ if (N >= (MAX_RECENT_TASKS-1)) {
+ final TaskRecord tr = mRecentTasks.remove(N - 1);
+ tr.disposeThumbnail();
+ tr.closeRecentsChain();
+ }
+
+ mRecentTasks.add(task);
+ r.task.stack.addTask(task, false, false);
+
+ task.setLastThumbnail(thumbnail);
+ task.freeLastThumbnail();
+
+ return task.taskId;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(callingIdent);
+ }
+ }
+
+ @Override
+ public Point getAppTaskThumbnailSize() {
+ synchronized (this) {
+ return new Point(mThumbnailWidth, mThumbnailHeight);
+ }
+ }
+
+ @Override
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription td) {
synchronized (this) {
ActivityRecord r = ActivityRecord.isInStackLocked(token);
@@ -18056,14 +18198,16 @@
mCallingUid = callingUid;
}
+ private void checkCaller() {
+ if (mCallingUid != Binder.getCallingUid()) {
+ throw new SecurityException("Caller " + mCallingUid
+ + " does not match caller of getAppTasks(): " + Binder.getCallingUid());
+ }
+ }
+
@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;
- }
+ checkCaller();
synchronized (ActivityManagerService.this) {
long origId = Binder.clearCallingIdentity();
@@ -18085,12 +18229,7 @@
@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;
- }
+ checkCaller();
synchronized (ActivityManagerService.this) {
long origId = Binder.clearCallingIdentity();
@@ -18105,5 +18244,28 @@
return null;
}
}
+
+ @Override
+ public void setExcludeFromRecents(boolean exclude) {
+ checkCaller();
+
+ synchronized (ActivityManagerService.this) {
+ 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);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index e309a03..5ad84d4 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -355,8 +355,8 @@
mCurrentUser = mService.mCurrentUserId;
// Get the activity screenshot thumbnail dimensions
Resources res = mService.mContext.getResources();
- mThumbnailWidth = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width);
- mThumbnailHeight = res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height);
+ mThumbnailWidth = mService.mThumbnailWidth;
+ mThumbnailHeight = mService.mThumbnailHeight;
}
/**
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index ccca657..48cb61a 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -167,6 +167,34 @@
setIntent(_intent, info);
}
+ TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
+ ActivityManager.TaskDescription _taskDescription) {
+ mService = service;
+ mFilename = String.valueOf(_taskId) + TASK_THUMBNAIL_SUFFIX +
+ TaskPersister.IMAGE_EXTENSION;
+ mLastThumbnailFile = new File(TaskPersister.sImagesDir, mFilename);
+ taskId = _taskId;
+ mAffiliatedTaskId = _taskId;
+ voiceSession = null;
+ voiceInteractor = null;
+ mActivities = new ArrayList<ActivityRecord>();
+ setIntent(_intent, info);
+
+ taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+ isPersistable = true;
+ mCallingUid = info.applicationInfo.uid;
+ mCallingPackage = info.packageName;
+ // Clamp to [1, 100].
+ maxRecents = Math.min(Math.max(info.maxRecents, 1), 100);
+
+ taskType = APPLICATION_ACTIVITY_TYPE;
+ mTaskToReturnTo = HOME_ACTIVITY_TYPE;
+ userId = UserHandle.getUserId(info.applicationInfo.uid);
+ lastTaskDescription = _taskDescription;
+ mCallingUid = info.applicationInfo.uid;
+ mCallingPackage = info.packageName;
+ }
+
TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
String _affinity, ComponentName _realActivity, ComponentName _origActivity,
boolean _rootWasReset, boolean _autoRemoveRecents, boolean _askedCompatMode,
@@ -765,7 +793,7 @@
}
void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
- Slog.i(TAG, "Saving task=" + this);
+ if (ActivityManagerService.DEBUG_RECENTS) Slog.i(TAG, "Saving task=" + this);
out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
if (realActivity != null) {
@@ -948,18 +976,15 @@
activities.get(activityNdx).task = task;
}
- Slog.i(TAG, "Restored task=" + task);
+ if (ActivityManagerService.DEBUG_RECENTS) Slog.d(TAG, "Restored task=" + task);
return task;
}
void dump(PrintWriter pw, String prefix) {
- if (rootWasReset || userId != 0 || numFullscreen != 0) {
- pw.print(prefix); pw.print(" rootWasReset="); pw.print(rootWasReset);
- pw.print(" userId="); pw.print(userId);
- pw.print(" taskType="); pw.print(taskType);
- pw.print(" numFullscreen="); pw.print(numFullscreen);
- pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
- }
+ pw.print(prefix); pw.print("userId="); pw.print(userId);
+ pw.print(" creatorUid="); pw.print(creatorUid);
+ pw.print(" mCallingUid="); pw.print(mCallingUid);
+ pw.print(" mCallingPackage="); pw.println(mCallingPackage);
if (affinity != null) {
pw.print(prefix); pw.print("affinity="); pw.println(affinity);
}
@@ -991,6 +1016,17 @@
pw.print(prefix); pw.print("realActivity=");
pw.println(realActivity.flattenToShortString());
}
+ if (autoRemoveRecents || taskType != 0 || mTaskToReturnTo != 0 || numFullscreen != 0) {
+ pw.print(prefix); pw.print("autoRemoveRecents="); pw.print(autoRemoveRecents);
+ pw.print(" numFullscreen="); pw.print(numFullscreen);
+ pw.print(" taskType="); pw.print(taskType);
+ pw.print(" mTaskToReturnTo="); pw.println(mTaskToReturnTo);
+ }
+ if (rootWasReset || mNeverRelinquishIdentity || mReuseTask) {
+ pw.print(prefix); pw.print("rootWasReset="); pw.print(rootWasReset);
+ pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity);
+ pw.print(" mReuseTask="); pw.println(mReuseTask);
+ }
pw.print(prefix); pw.print("Activities="); pw.println(mActivities);
if (!askedCompatMode) {
pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode);
diff --git a/tests/ActivityTests/res/drawable-hdpi/icon.png b/tests/ActivityTests/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..2eab6f2
--- /dev/null
+++ b/tests/ActivityTests/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/tests/ActivityTests/res/drawable-mdpi/icon.png b/tests/ActivityTests/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..63aec6f
--- /dev/null
+++ b/tests/ActivityTests/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/tests/ActivityTests/res/drawable-xhdpi/icon.png b/tests/ActivityTests/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..8ea9c48
--- /dev/null
+++ b/tests/ActivityTests/res/drawable-xhdpi/icon.png
Binary files differ
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 9002125..7f3aa77 100644
--- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
+++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java
@@ -28,6 +28,7 @@
import android.content.ContentProviderClient;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -219,7 +220,7 @@
@Override public boolean onMenuItemClick(MenuItem item) {
Intent intent = new Intent(ActivityTestMain.this, UserTarget.class);
sendOrderedBroadcastAsUser(intent, new UserHandle(mSecondUser), null,
- new BroadcastResultReceiver(),
+ new BroadcastResultReceiver(),
null, Activity.RESULT_OK, null, null);
return true;
}
@@ -291,6 +292,30 @@
return true;
}
});
+ menu.add("Add App Recent").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ addAppRecents(1);
+ return true;
+ }
+ });
+ menu.add("Add App 10x Recent").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ addAppRecents(10);
+ return true;
+ }
+ });
+ menu.add("Exclude!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ setExclude(true);
+ return true;
+ }
+ });
+ menu.add("Include!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override public boolean onMenuItemClick(MenuItem item) {
+ setExclude(false);
+ return true;
+ }
+ });
return true;
}
@@ -317,7 +342,36 @@
mConnections.clear();
}
- private View scrollWrap(View view) {
+ void addAppRecents(int count) {
+ ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_LAUNCHER);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ intent.setComponent(new ComponentName(this, ActivityTestMain.class));
+ for (int i=0; i<count; i++) {
+ ActivityManager.TaskDescription desc = new ActivityManager.TaskDescription();
+ desc.setLabel("Added #" + i);
+ Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon);
+ if ((i&1) == 0) {
+ desc.setIcon(bitmap);
+ }
+ int taskId = am.addAppTask(this, intent, desc, bitmap);
+ Log.i(TAG, "Added new task id #" + taskId);
+ }
+ }
+
+ void setExclude(boolean exclude) {
+ ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
+ List<ActivityManager.AppTask> tasks = am.getAppTasks();
+ int taskId = getTaskId();
+ for (int i=0; i<tasks.size(); i++) {
+ ActivityManager.AppTask task = tasks.get(i);
+ if (task.getTaskInfo().id == taskId) {
+ task.setExcludeFromRecents(exclude);
+ }
+ }
+ }
+ private View scrollWrap(View view) {
ScrollView scroller = new ScrollView(this);
scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT,
ScrollView.LayoutParams.MATCH_PARENT));