am 8c6c54ec: am a7d4d7bc: am ee6e179e: Merge "Fix issue #17038762: Add API to add entries to the recents list" into lmp-dev

* commit '8c6c54ecfd281751925ff6df566e154c2f8aec60':
  Fix issue #17038762: Add API to add entries to the recents list
diff --git a/api/current.txt b/api/current.txt
index c36bb8c..faab3d7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3596,8 +3596,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();
@@ -3631,6 +3634,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 100644
--- 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));