API for launching activities to the side in side-by-side mode.
This API is intended for side-by-side mode, so that when a new activity
is launched, it will show up on the other side instead of covering the
launching activity.
Bug: 26141281
Change-Id: I97d7f2f48d42a31cfb1a86821474582b9c5d9e45
diff --git a/api/current.txt b/api/current.txt
index 0db5f25..43002e2 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8488,6 +8488,7 @@
field public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; // 0x800000
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_LAUNCH_TO_SIDE = 4096; // 0x1000
field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
diff --git a/api/system-current.txt b/api/system-current.txt
index 22da8c5..c7ee972 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8756,6 +8756,7 @@
field public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; // 0x800000
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_LAUNCH_TO_SIDE = 4096; // 0x1000
field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
diff --git a/api/test-current.txt b/api/test-current.txt
index 0d55248..f911b44 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8488,6 +8488,7 @@
field public static final int FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS = 8388608; // 0x800000
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_LAUNCH_TO_SIDE = 4096; // 0x1000
field public static final int FLAG_ACTIVITY_MULTIPLE_TASK = 134217728; // 0x8000000
field public static final int FLAG_ACTIVITY_NEW_DOCUMENT = 524288; // 0x80000
field public static final int FLAG_ACTIVITY_NEW_TASK = 268435456; // 0x10000000
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index a958953..9973faf 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4433,6 +4433,13 @@
public static final int FLAG_ACTIVITY_RETAIN_IN_RECENTS = 0x00002000;
/**
+ * This flag is only used in the multi-window mode. The new activity will be displayed on
+ * the other side than the one that is launching it. This can only be used in conjunction with
+ * {@link #FLAG_ACTIVITY_NEW_TASK}.
+ */
+ public static final int FLAG_ACTIVITY_LAUNCH_TO_SIDE = 0x00001000;
+
+ /**
* If set, when sending a broadcast only registered receivers will be
* called -- no BroadcastReceiver components will be launched.
*/
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ea7dff0..927a7b6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -492,7 +492,7 @@
// Used to indicate that a task is removed it should also be removed from recents.
private static final boolean REMOVE_FROM_RECENTS = true;
// Used to indicate that an app transition should be animated.
- private static final boolean ANIMATE = true;
+ static final boolean ANIMATE = true;
// Determines whether to take full screen screenshots
static final boolean TAKE_FULLSCREEN_SCREENSHOTS = true;
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index be2c49a..810f037 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -17,7 +17,11 @@
package com.android.server.am;
import static android.Manifest.permission.START_ANY_ACTIVITY;
-import static android.app.ActivityManager.*;
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
+import static android.app.ActivityManager.RESIZE_MODE_FORCED;
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
@@ -29,18 +33,61 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_TO_SIDE;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static com.android.server.am.ActivityManagerDebugConfig.*;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONTAINERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_IDLE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKSCREEN;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PAUSE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RELEASE;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RESULTS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USER_LEAVING;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONTAINERS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IDLE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_PAUSE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RELEASE;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RESULTS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAVING;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ANIMATE;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
+import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
-import static com.android.server.am.ActivityStack.ActivityState.*;
+import static com.android.server.am.ActivityStack.ActivityState.DESTROYED;
+import static com.android.server.am.ActivityStack.ActivityState.DESTROYING;
+import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING;
+import static com.android.server.am.ActivityStack.ActivityState.PAUSED;
+import static com.android.server.am.ActivityStack.ActivityState.PAUSING;
+import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
+import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
@@ -50,6 +97,7 @@
import android.Manifest;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityManager.StackId;
import android.app.ActivityManager.StackInfo;
import android.app.ActivityOptions;
@@ -58,12 +106,11 @@
import android.app.IActivityContainer;
import android.app.IActivityContainerCallback;
import android.app.IActivityManager;
+import android.app.IActivityManager.WaitResult;
import android.app.IApplicationThread;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.ProfilerInfo;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.IActivityManager.WaitResult;
import android.app.ResultInfo;
import android.app.StatusBarManager;
import android.app.admin.IDevicePolicyManager;
@@ -115,7 +162,6 @@
import android.util.EventLog;
import android.util.Slog;
import android.util.SparseArray;
-
import android.util.SparseIntArray;
import android.view.Display;
import android.view.DisplayInfo;
@@ -1927,14 +1973,17 @@
return ACTIVITY_RESTRICTION_NONE;
}
- private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) {
+ private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds,
+ int launchFlags) {
final TaskRecord task = r.task;
-
if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
return mHomeStack;
}
- ActivityStack stack;
+ ActivityStack stack = getLaunchToSideStack(r, launchFlags, task);
+ if (stack != null) {
+ return stack;
+ }
if (task != null && task.stack != null) {
stack = task.stack;
@@ -1942,10 +1991,10 @@
if (mFocusedStack != stack) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
"computeStackFocus: Setting " + "focused stack to r=" + r
- + " task=" + task);
+ + " task=" + task);
} else {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
- "computeStackFocus: Focused stack already=" + mFocusedStack);
+ "computeStackFocus: Focused stack already=" + mFocusedStack);
}
}
return stack;
@@ -1960,11 +2009,13 @@
// The fullscreen stack can contain any task regardless of if the task is resizeable
// or not. So, we let the task go in the fullscreen task if it is the focus stack.
- // If the freeform stack has focus, and the activity to be launched is resizeable,
+ // If the freeform or docked stack has focus, and the activity to be launched is resizeable,
// we can also put it in the focused stack.
+ final int focusedStackId = mFocusedStack.mStackId;
final boolean canUseFocusedStack =
- mFocusedStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID
- || mFocusedStack.mStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable;
+ focusedStackId == FULLSCREEN_WORKSPACE_STACK_ID
+ || focusedStackId == DOCKED_STACK_ID
+ || (focusedStackId == FREEFORM_WORKSPACE_STACK_ID && r.info.resizeable);
if (canUseFocusedStack
&& (!newTask || mFocusedStack.mActivityContainer.isEligibleForNewTasks())) {
if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
@@ -1993,6 +2044,33 @@
return stack;
}
+ private ActivityStack getLaunchToSideStack(ActivityRecord r, int launchFlags, TaskRecord task) {
+ if ((launchFlags & FLAG_ACTIVITY_LAUNCH_TO_SIDE) == 0) {
+ return null;
+ }
+ // The parent activity doesn't want to launch the activity on top of itself, but
+ // instead tries to put it onto other side in side-by-side mode.
+ final ActivityStack parentStack = task != null ? task.stack
+ : r.mInitialActivityContainer != null ? r.mInitialActivityContainer.mStack
+ : mFocusedStack;
+ if (parentStack != null && parentStack.mStackId == DOCKED_STACK_ID) {
+ // If parent was in docked stack, the natural place to launch another activity
+ // will be fullscreen, so it can appear alongside the docked window.
+ return getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+ } else {
+ // If the parent is not in the docked stack, we check if there is docked window
+ // and if yes, we will launch into that stack. If not, we just put the new
+ // activity into parent's stack, because we can't find a better place.
+ final ActivityStack stack = getStack(DOCKED_STACK_ID);
+ if (stack != null && !stack.isStackVisibleLocked()) {
+ // There is a docked stack, but it isn't visible, so we can't launch into that.
+ return null;
+ } else {
+ return stack;
+ }
+ }
+ }
+
boolean setFocusedStack(ActivityRecord r, String reason) {
if (r == null) {
// Not sure what you are trying to do, but it is not going to work...
@@ -2261,6 +2339,7 @@
// Now that we are actually launching it, we can assign the base intent.
intentActivity.task.setIntent(r);
}
+
targetStack = intentActivity.task.stack;
targetStack.mLastPausedActivity = null;
// If the target task is not in the front, then we need
@@ -2283,9 +2362,15 @@
intentActivity.setTaskToAffiliateWith(sourceRecord.task);
}
movedHome = true;
- targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
- options, r.appTimeTracker, "bringingFoundTaskToFront");
- movedToFront = true;
+ final ActivityStack sideStack = getLaunchToSideStack(r, launchFlags, r.task);
+ if (sideStack == null || sideStack == targetStack) {
+ // We only want to move to the front, if we aren't going to launch on a
+ // different stack. If we launch on a different stack, we will put the
+ // task on top there.
+ targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
+ options, r.appTimeTracker, "bringingFoundTaskToFront");
+ movedToFront = true;
+ }
if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
// Caller wants to appear on home activity.
@@ -2362,7 +2447,7 @@
// Target stack got cleared when we all activities were removed
// above. Go ahead and reset it.
targetStack = computeStackFocus(
- sourceRecord, false /* newTask */, null /* bounds */);
+ sourceRecord, false /* newTask */, null /* bounds */, launchFlags);
targetStack.addTask(task,
!launchTaskBehind /* toTop */, "startActivityUnchecked");
}
@@ -2486,7 +2571,7 @@
if (r.resultTo == null && inTask == null && !addingToTask
&& (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
- targetStack = computeStackFocus(r, newTask, newBounds);
+ targetStack = computeStackFocus(r, newTask, newBounds, launchFlags);
if (doResume) {
targetStack.moveToFront("startingNewTask");
}
@@ -2524,7 +2609,19 @@
Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r);
return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
- targetStack = sourceTask.stack;
+ targetStack = null;
+ if (sourceTask.stack.topTask() != sourceTask) {
+ // We only want to allow changing stack if the target task is not the top one,
+ // otherwise we would move the launching task to the other side, rather than show
+ // two side by side.
+ targetStack = getLaunchToSideStack(r, launchFlags, r.task);
+ }
+ if (targetStack == null) {
+ targetStack = sourceTask.stack;
+ } else if (targetStack != sourceTask.stack) {
+ moveTaskToStackLocked(sourceTask.taskId, targetStack.mStackId, ON_TOP,
+ FORCE_FOCUS, "launchToSide", !ANIMATE);
+ }
if (doResume) {
targetStack.moveToFront("sourceStackToFront");
}
@@ -2629,7 +2726,7 @@
// This not being started from an existing activity, and not part
// of a new task... just put it in the top task, though these days
// this case should never happen.
- targetStack = computeStackFocus(r, newTask, null /* bounds */);
+ targetStack = computeStackFocus(r, newTask, null /* bounds */, launchFlags);
if (doResume) {
targetStack.moveToFront("addingToTopTask");
}