diff options
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/content/Intent.java | 55 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityManagerService.java | 37 | ||||
| -rwxr-xr-x | services/core/java/com/android/server/am/ActivityStack.java | 44 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityStackSupervisor.java | 19 |
5 files changed, 120 insertions, 36 deletions
diff --git a/api/current.txt b/api/current.txt index c739dea7d709..9a30c44227b6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6849,6 +6849,7 @@ package android.content { 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 96479e266781..0175d6289e68 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -3457,7 +3457,16 @@ public class Intent implements Parcelable, Cloneable { */ 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 @@ public class Intent implements Parcelable, Cloneable { * 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 class Intent implements Parcelable, Cloneable { */ 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 @@ public class Intent implements Parcelable, Cloneable { * @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 @@ public class Intent implements Parcelable, Cloneable { 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 cd9c9200d38a..ab7d60aa4118 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 @@ public final class ActivityManagerService extends ActivityManagerNative 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 34cc22ad2f2e..25292a0ed3c2 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { 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 @@ final class ActivityStack { // 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 @@ final class ActivityStack { + " 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 class ActivityStack { 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 3c7ef6110d59..3555993fa91d 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + 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 @@ public final class ActivityStackSupervisor implements DisplayListener { // 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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 @@ public final class ActivityStackSupervisor implements DisplayListener { 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, |