Launch new tasks with Doc Centric flag.

Introduction of new Intent flag FLAG_ACTIVITY_NEW_DOCUMENT. When
this flag is set the target activity will be launched in its own
task. This is the start of the new Doc Centric mode of working.

Change-Id: I719168532134ab2c5ea3300df676c2b2a0e81795
diff --git a/api/current.txt b/api/current.txt
index c739dea..9a30c44 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6849,6 +6849,7 @@
     field public static final int FLAG_ACTIVITY_FORWARD_RESULT = 33554432; // 0x2000000
     field public static final int FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = 1048576; // 0x100000
     field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
+    field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 268959744; // 0x10080000
     field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
     field public static final int FLAG_ACTIVITY_NO_ANIMATION = 65536; // 0x10000
     field public static final int FLAG_ACTIVITY_NO_HISTORY = 1073741824; // 0x40000000
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 96479e2..0175d62 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3457,7 +3457,16 @@
      */
     public static final int FLAG_ACTIVITY_NEW_TASK = 0x10000000;
     /**
-     * <strong>Do not use this flag unless you are implementing your own
+     * This flag is used to create a new task and launch an activity into it.
+     * This flag is always paired with either {@link #FLAG_ACTIVITY_NEW_DOCUMENT}
+     * or {@link #FLAG_ACTIVITY_NEW_TASK}. In both cases these flags alone would
+     * search through existing tasks for ones matching this Intent. Only if no such
+     * task is found would a new task be created. When paired with
+     * FLAG_ACTIVITY_MULTIPLE_TASK both of these behaviors are modified to skip
+     * the search for a matching task and unconditionally start a new task.
+     *
+     * <strong>When used with {@link #FLAG_ACTIVITY_NEW_TASK} do not use this
+     * flag unless you are implementing your own
      * top-level application launcher.</strong>  Used in conjunction with
      * {@link #FLAG_ACTIVITY_NEW_TASK} to disable the
      * behavior of bringing an existing task to the foreground.  When set,
@@ -3469,12 +3478,18 @@
      * you should not use this flag unless you provide some way for a user to
      * return back to the tasks you have launched.</strong>
      *
-     * <p>This flag is ignored if
-     * {@link #FLAG_ACTIVITY_NEW_TASK} is not set.
+     * See {@link #FLAG_ACTIVITY_NEW_DOCUMENT} for details of this flag's use for
+     * creating new document tasks.
+     *
+     * <p>This flag is ignored if one of {@link #FLAG_ACTIVITY_NEW_TASK} or
+     * {@link #FLAG_ACTIVITY_NEW_TASK} is not also set.
      *
      * <p>See
      * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back
      * Stack</a> for more information about tasks.
+     *
+     * @see #FLAG_ACTIVITY_NEW_DOCUMENT
+     * @see #FLAG_ACTIVITY_NEW_TASK
      */
     public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 0x08000000;
     /**
@@ -3581,6 +3596,34 @@
      */
     public static final int FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET = 0x00080000;
     /**
+     * This flag is used to break out "documents" into separate tasks that can
+     * be reached via the Recents mechanism. Such a document is any kind of
+     * item for which an application may want to maintain multiple simultaneous
+     * instances. Examples might be text files, web pages, spreadsheets, or
+     * emails. Each such document will be in a separate task in the Recents list.
+     *
+     * <p>When set, the activity specified by this Intent will launch into a
+     * separate task rooted at that activity. The activity launched must be
+     * defined with {@link android.R.attr#launchMode} "standard" or "singleTop".
+     *
+     * <p>If FLAG_ACTIVITY_NEW_DOCUMENT is used without
+     * {@link #FLAG_ACTIVITY_MULTIPLE_TASK} then the activity manager will
+     * search for an existing task with a matching target activity and Intent
+     * data URI and relaunch that task, first finishing all activities down to
+     * the root activity and then calling the root activity's
+     * {@link android.app.Activity#onNewIntent(Intent)} method. If no existing
+     * task's root activity matches the Intent's data URI then a new task will
+     * be launched with the target activity as root.
+     *
+     * <p>When paired with {@link #FLAG_ACTIVITY_MULTIPLE_TASK} this will
+     * always create a new task. Thus the same document may be made to appear
+     * more than one time in Recents.
+     *
+     * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+     */
+    public static final int FLAG_ACTIVITY_NEW_DOCUMENT =
+            FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET | FLAG_ACTIVITY_NEW_TASK;
+    /**
      * If set, this flag will prevent the normal {@link android.app.Activity#onUserLeaveHint}
      * callback from occurring on the current frontmost activity before it is
      * paused as the newly-started activity is brought to the front.
@@ -6246,6 +6289,7 @@
      * @see #FLAG_ACTIVITY_FORWARD_RESULT
      * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
      * @see #FLAG_ACTIVITY_MULTIPLE_TASK
+     * @see #FLAG_ACTIVITY_NEW_DOCUMENT
      * @see #FLAG_ACTIVITY_NEW_TASK
      * @see #FLAG_ACTIVITY_NO_ANIMATION
      * @see #FLAG_ACTIVITY_NO_HISTORY
@@ -7342,4 +7386,9 @@
         String htmlText = htmlTexts != null ? htmlTexts.get(which) : null;
         return new ClipData.Item(text, htmlText, null, uri);
     }
+
+    /** @hide */
+    public boolean isDocument() {
+        return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index cd9c920..ab7d60a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3319,20 +3319,35 @@
             return;
         }
         // Remove any existing entries that are the same kind of task.
+        final Intent intent = task.intent;
+        final boolean document = intent != null && intent.isDocument();
         for (int i=0; i<N; i++) {
             TaskRecord tr = mRecentTasks.get(i);
-            if (task.userId == tr.userId
-                    && ((task.affinity != null && task.affinity.equals(tr.affinity))
-                    || (task.intent != null && task.intent.filterEquals(tr.intent)))) {
-                tr.disposeThumbnail();
-                mRecentTasks.remove(i);
-                i--;
-                N--;
-                if (task.intent == null) {
-                    // If the new recent task we are adding is not fully
-                    // specified, then replace it with the existing recent task.
-                    task = tr;
+            if (task != tr) {
+                if (task.userId != tr.userId) {
+                    continue;
                 }
+                final Intent trIntent = tr.intent;
+                if ((task.affinity == null || !task.affinity.equals(tr.affinity)) &&
+                    (intent == null || !intent.filterEquals(trIntent))) {
+                    continue;
+                }
+                if (document || trIntent != null && trIntent.isDocument()) {
+                    // Document tasks do not match other tasks.
+                    continue;
+                }
+            }
+
+            // Either task and tr are the same or, their affinities match or their intents match
+            // and neither of them is a document.
+            tr.disposeThumbnail();
+            mRecentTasks.remove(i);
+            i--;
+            N--;
+            if (task.intent == null) {
+                // If the new recent task we are adding is not fully
+                // specified, then replace it with the existing recent task.
+                task = tr;
             }
         }
         if (N >= MAX_RECENT_TASKS) {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 34cc22a..25292a0 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -492,6 +492,9 @@
             cls = new ComponentName(info.packageName, info.targetActivity);
         }
         final int userId = UserHandle.getUserId(info.applicationInfo.uid);
+        boolean isDocument = intent != null & intent.isDocument();
+        // If documentData is non-null then it must match the existing task data.
+        Uri documentData = isDocument ? intent.getData() : null;
 
         if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this);
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
@@ -508,23 +511,39 @@
                 continue;
             }
 
+            final Intent taskIntent = task.intent;
+            final Intent affinityIntent = task.affinityIntent;
+            final boolean taskIsDocument;
+            final Uri taskDocumentData;
+            if (taskIntent != null && taskIntent.isDocument()) {
+                taskIsDocument = true;
+                taskDocumentData = taskIntent.getData();
+            } else if (affinityIntent != null && affinityIntent.isDocument()) {
+                taskIsDocument = true;
+                taskDocumentData = affinityIntent.getData();
+            } else {
+                taskIsDocument = false;
+                taskDocumentData = null;
+            }
+
             if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls="
-                    + r.task.intent.getComponent().flattenToShortString()
+                    + taskIntent.getComponent().flattenToShortString()
                     + "/aff=" + r.task.affinity + " to new cls="
                     + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity);
-            if (task.affinity != null) {
-                if (task.affinity.equals(info.taskAffinity)) {
+            if (!isDocument && !taskIsDocument && task.affinity != null) {
+                if (task.affinity.equals(target.taskAffinity)) {
                     if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!");
                     return r;
                 }
-            } else if (task.intent != null && task.intent.getComponent().equals(cls)) {
+            } else if (taskIntent != null && taskIntent.getComponent().equals(cls) &&
+                    Objects.equals(documentData, taskDocumentData)) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
                 //dump();
                 if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
                         + r.intent);
                 return r;
-            } else if (task.affinityIntent != null
-                    && task.affinityIntent.getComponent().equals(cls)) {
+            } else if (affinityIntent != null && affinityIntent.getComponent().equals(cls) &&
+                    Objects.equals(documentData, taskDocumentData)) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!");
                 //dump();
                 if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: "
@@ -1857,7 +1876,7 @@
                 // If the caller has requested that the target task be
                 // reset, then do so.
                 if ((r.intent.getFlags()
-                        &Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+                        & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                     resetTaskIfNeededLocked(r, r);
                     doShow = topRunningNonDelayedActivityLocked(null) == r;
                 }
@@ -2006,14 +2025,7 @@
                             + " out to new task " + target.task);
                 }
 
-                if (clearWhenTaskReset) {
-                    // This is the start of a new sub-task.
-                    if (target.thumbHolder == null) {
-                        target.thumbHolder = new ThumbnailHolder();
-                    }
-                } else {
-                    target.thumbHolder = newThumbHolder;
-                }
+                target.thumbHolder = newThumbHolder;
 
                 final int targetTaskId = targetTask.taskId;
                 mWindowManager.setAppGroupId(target.appToken, targetTaskId);
@@ -2484,7 +2496,7 @@
         final int index = activities.indexOf(r);
         if (index < (activities.size() - 1)) {
             task.setFrontOfTask();
-            if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
+            if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) {
                 // If the caller asked that this activity (and all above it)
                 // be cleared when the task is reset, don't lose that information,
                 // but propagate it up to the next activity.
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 3c7ef61..3555993 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,6 +17,7 @@
 package com.android.server.am;
 
 import static android.Manifest.permission.START_ANY_ACTIVITY;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -1443,14 +1444,20 @@
             }
         }
 
+        final boolean newDocument = intent.isDocument();
         if (sourceRecord == null) {
             // This activity is not being started from another...  in this
             // case we -always- start a new task.
-            if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+            if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
                 Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                         "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
                 launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
             }
+        } else if (newDocument) {
+            if (r.launchMode != ActivityInfo.LAUNCH_MULTIPLE) {
+                Slog.w(TAG, "FLAG_ACTIVITY_NEW_DOCUMENT and launchMode != \"standard\"");
+                r.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
+            }
         } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
             // The original activity who is starting us is running as a single
             // instance...  this new activity it is starting must go on its
@@ -1473,7 +1480,7 @@
                 // so we don't want to blindly throw it in to that task.  Instead we will take
                 // the NEW_TASK flow and try to find a task for it. But save the task information
                 // so it can be used when creating the new task.
-                if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+                if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
                     Slog.w(TAG, "startActivity called from finishing " + sourceRecord
                             + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
                     launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -1489,7 +1496,7 @@
             sourceStack = null;
         }
 
-        if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+        if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
             // For whatever reason this activity is being launched into a new
             // task...  yet the caller has requested a result back.  Well, that
             // is pretty messed up, so instead immediately send back a cancel
@@ -1506,8 +1513,8 @@
         boolean movedHome = false;
         TaskRecord reuseTask = null;
         ActivityStack targetStack;
-        if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
-                (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
+        if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
+                (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
                 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK
                 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
             // If bring to front is requested, and no result is requested, and
@@ -1696,7 +1703,7 @@
             if (top != null && r.resultTo == null) {
                 if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
                     if (top.app != null && top.app.thread != null) {
-                        if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
+                        if ((launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
                             || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP
                             || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) {
                             ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top,