Merge "Resize task to fullscreen when moving from pinned stack."
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6be5775..300c7df 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -135,6 +135,8 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -144,6 +146,8 @@
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import static java.lang.Integer.MAX_VALUE;
+
import android.Manifest;
import android.Manifest.permission;
import android.annotation.NonNull;
@@ -9808,14 +9812,19 @@
} else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
stackId = FREEFORM_WORKSPACE_STACK_ID;
}
+
+ // Reparent the task to the right stack if necessary
boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
if (stackId != task.getStackId()) {
- mStackSupervisor.moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
- !FORCE_FOCUS, "resizeTask");
+ // Defer resume until the task is resized below
+ task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+ DEFER_RESUME, "resizeTask");
preserveWindow = false;
}
- task.resize(bounds, resizeMode, preserveWindow, false /* deferResume */);
+ // After reparenting (which only resizes the task to the stack bounds), resize the
+ // task to the actual bounds provided
+ task.resize(bounds, resizeMode, preserveWindow, !DEFER_RESUME);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -10194,14 +10203,16 @@
throw new IllegalArgumentException(
"exitFreeformMode: No activity record matching token=" + token);
}
- final ActivityStack stack = r.getStackLocked(token);
+
+ final ActivityStack stack = r.getStack();
if (stack == null || stack.mStackId != FREEFORM_WORKSPACE_STACK_ID) {
throw new IllegalStateException(
"exitFreeformMode: You can only go fullscreen from freeform.");
}
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
- mStackSupervisor.moveTaskToStackLocked(r.task.taskId, FULLSCREEN_WORKSPACE_STACK_ID,
- ON_TOP, !FORCE_FOCUS, "exitFreeformMode", ANIMATE);
+ r.task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT,
+ ANIMATE, !DEFER_RESUME, "exitFreeformMode");
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -10218,15 +10229,22 @@
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "moveTaskToStack: No task for id=" + taskId);
+ return;
+ }
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToStack: moving task=" + taskId
+ " to stackId=" + stackId + " toTop=" + toTop);
if (stackId == DOCKED_STACK_ID) {
mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
null /* initialBounds */);
}
- boolean result = mStackSupervisor.moveTaskToStackLocked(taskId, stackId, toTop,
- !FORCE_FOCUS, "moveTaskToStack", ANIMATE);
- if (result && stackId == DOCKED_STACK_ID) {
+
+ final boolean successful = task.reparent(stackId, toTop,
+ REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
+ if (successful && stackId == DOCKED_STACK_ID) {
// If task moved to docked stack - show recents if needed.
mWindowManager.showRecentApps(false /* fromHome */);
}
@@ -10258,22 +10276,23 @@
// TODO: App transition
mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_RELAUNCH, false);
- // Defer the resume so resume/pausing while moving stacks is dangerous.
- mStackSupervisor.moveTaskToStackLocked(topTask.taskId, DOCKED_STACK_ID,
- false /* toTop */, !FORCE_FOCUS, "swapDockedAndFullscreenStack",
- ANIMATE, true /* deferResume */);
+ // Defer the resume until we move all the docked tasks to the fullscreen stack below
+ topTask.reparent(DOCKED_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+ DEFER_RESUME, "swapDockedAndFullscreenStack - DOCKED_STACK");
final int size = tasks.size();
for (int i = 0; i < size; i++) {
final int id = tasks.get(i).taskId;
if (id == topTask.taskId) {
continue;
}
- mStackSupervisor.moveTaskToStackLocked(id,
- FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */, !FORCE_FOCUS,
- "swapDockedAndFullscreenStack", ANIMATE, true /* deferResume */);
+
+ // Defer the resume until after all the tasks have been moved
+ tasks.get(i).reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+ REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, DEFER_RESUME,
+ "swapDockedAndFullscreenStack - FULLSCREEN_STACK");
}
- // Because we deferred the resume, to avoid conflicts with stack switches while
+ // Because we deferred the resume to avoid conflicts with stack switches while
// resuming, we need to do it after all the tasks are moved.
mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
mStackSupervisor.resumeFocusedStackTopActivityLocked();
@@ -10306,12 +10325,20 @@
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
+ return false;
+ }
+
if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
+ " to createMode=" + createMode + " toTop=" + toTop);
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
- final boolean moved = mStackSupervisor.moveTaskToStackLocked(
- taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
- animate, DEFER_RESUME);
+
+ // Defer resuming until we move the home stack to the front below
+ final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+ REPARENT_KEEP_STACK_AT_FRONT, animate, DEFER_RESUME,
+ "moveTaskToDockedStack");
if (moved) {
if (moveHomeStackFront) {
mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
@@ -10445,7 +10472,8 @@
stack.positionChildAt(task, position);
} else {
// Reparent to new stack.
- task.reparent(stackId, position, "positionTaskInStack");
+ task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
+ !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
}
} finally {
Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 10055c8..c79ed2a 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -797,6 +797,15 @@
+ " is already the parent of r=" + this);
}
+ // TODO: Ensure that we do not directly reparent activities across stacks, as that may leave
+ // the stacks in strange states. For now, we should use Task.reparent() to ensure that
+ // the stack is left in an OK state.
+ if (prevTask != null && newTask != null && prevTask.getStack() != newTask.getStack()) {
+ throw new IllegalArgumentException(reason + ": task=" + newTask
+ + " is in a different stack (" + newTask.getStackId() + ") than the parent of"
+ + " r=" + this + " (" + prevTask.getStackId() + ")");
+ }
+
// Must reparent first in window manager
mWindowContainerController.reparent(newTask.getWindowContainerController(), position);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 827a41e..5d4bff9 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -5077,45 +5077,6 @@
moveToFront(reason);
}
- /**
- * Moves the input activity from its current stack to this one.
- * NOTE: The current task of the activity isn't moved to this stack. Instead a new task is
- * created on this stack which the activity is added to.
- * */
- void moveActivityToStack(ActivityRecord r) {
- final ActivityStack prevStack = r.getStack();
- if (prevStack.mStackId == mStackId) {
- // You are already in the right stack silly...
- return;
- }
-
- final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
- && (mStackSupervisor.topRunningActivityLocked() == r);
- final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
- final boolean wasPaused = prevStack.mPausingActivity == r;
-
- // Create a new task for the activity to be parented in
- final TaskRecord task = createTaskRecord(
- mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
- r.info, r.intent, null, null, true, r.mActivityType);
- // This is a new task, so reparenting it to position 0 will move it to the top
- r.reparent(task, 0 /* position */, "moveActivityToStack");
-
- // Notify the task actiivties if it was moved to/from a pinned stack
- mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
-
- // Resume the activity if necessary after it has moved
- moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, wasPaused,
- "moveActivityToStack");
- if (wasResumed) {
- prevStack.mResumedActivity = null;
- }
- if (wasPaused) {
- prevStack.mPausingActivity = null;
- prevStack.removeTimeoutsForActivityLocked(r);
- }
- }
-
public int getStackId() {
return mStackId;
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index d3ad057..6e138a1 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -89,6 +89,9 @@
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_PINNABLE;
import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_WHITELISTED;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
+import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
import static java.lang.Integer.MAX_VALUE;
@@ -1992,8 +1995,8 @@
stackId = task.getLaunchStackId();
}
if (stackId != currentStack.mStackId) {
- currentStack = moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
- !FORCE_FOCUS, reason);
+ task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "findTaskToMoveToFrontLocked");
stackId = currentStack.mStackId;
// moveTaskToStackUncheckedLocked() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
@@ -2278,6 +2281,7 @@
if (onTop) {
for (int i = 0; i < size; i++) {
final TaskRecord task = tasks.get(i);
+ final boolean isTopTask = i == (size - 1);
if (fromStackId == PINNED_STACK_ID) {
// Update the return-to to reflect where the pinned stack task was moved
// from so that we retain the stack that was previously visible if the
@@ -2287,22 +2291,25 @@
MetricsLogger.action(mService.mContext,
MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
}
- moveTaskToStackLocked(tasks.get(i).taskId,
- FULLSCREEN_WORKSPACE_STACK_ID, onTop, onTop /*forceFocus*/,
- "moveTasksToFullscreenStack - onTop", ANIMATE, DEFER_RESUME);
+ // Defer resume until all the tasks have been moved to the fullscreen stack
+ task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+ REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
+ "moveTasksToFullscreenStack - onTop");
}
-
- ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
- resumeFocusedStackTopActivityLocked();
} else {
for (int i = 0; i < size; i++) {
final TaskRecord task = tasks.get(i);
- final int position = fullscreenStack != null ?
- Math.max(fullscreenStack.getAllTasks().size() - 1, 0) : 0;
+ final int position = fullscreenStack != null
+ ? Math.max(fullscreenStack.getAllTasks().size() - 1, 0) : 0;
+ // Defer resume until all the tasks have been moved to the fullscreen stack
task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, position,
- "moveTasksToFullscreenStack - NOT_onTop");
+ REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+ DEFER_RESUME, "moveTasksToFullscreenStack - NOT_onTop");
}
}
+
+ ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
+ resumeFocusedStackTopActivityLocked();
} finally {
mAllowDockedStackResize = true;
mWindowManager.continueSurfaceLayout();
@@ -2445,14 +2452,16 @@
? Math.max(0, fullscreenStack.getChildCount() - 1)
: fullscreenStack.getChildCount();
final TaskRecord task = tasks.get(i);
- task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition, "removeStack");
+ // Defer resume until we remove all the tasks
+ task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition,
+ REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME, "removeStack");
}
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
} else {
// If there is no fullscreen stack, then create the stack and move all the tasks
// onto the stack
- moveTasksToFullscreenStackLocked(PINNED_STACK_ID, false /* onTop */);
+ moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
}
} else {
for (int i = tasks.size() - 1; i >= 0; i--) {
@@ -2657,168 +2666,52 @@
}
/**
- * Moves the specified task record to the input stack id.
- * WARNING: This method performs an unchecked/raw move of the task and
- * can leave the system in an unstable state if used incorrectly.
- * Use {@link #moveTaskToStackLocked} to perform safe task movement to a stack.
- * @param task Task to move.
- * @param stackId Id of stack to move task to.
- * @param toTop True if the task should be placed at the top of the stack.
- * @param forceFocus if focus should be moved to the new stack
- * @param reason Reason the task is been moved.
- * @return The stack the task was moved to.
+ * Returns the reparent target stack, creating the stack if necessary. This call also enforces
+ * the various checks on tasks that are going to be reparented from one stack to another.
*/
- ActivityStack moveTaskToStackUncheckedLocked(TaskRecord task, int stackId, boolean toTop,
- boolean forceFocus, String reason) {
-
- if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
- throw new IllegalStateException("moveTaskToStackUncheckedLocked: Device doesn't "
- + "support multi-window task=" + task + " to stackId=" + stackId);
- }
-
- final ActivityRecord r = task.topRunningActivityLocked();
+ ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
final ActivityStack prevStack = task.getStack();
- final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
- final boolean wasResumed = prevStack.mResumedActivity == r;
- final boolean wasPaused = prevStack.mPausingActivity == r;
- // In some cases the focused stack isn't the front stack. E.g. pinned stack.
- // Whenever we are moving the top activity from the front stack we want to make sure to move
- // the stack to the front.
- final boolean wasFront = isFrontStackOnDisplay(prevStack)
- && (prevStack.topRunningActivityLocked() == r);
- if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
- // We don't allow moving a unresizeable task to the docked stack since the docked
- // stack is used for split-screen mode and will cause things like the docked divider to
- // show up. We instead leave the task in its current stack or move it to the fullscreen
- // stack if it isn't currently in a stack.
- stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
- Slog.w(TAG, "Can not move unresizeable task=" + task
- + " to docked stack. Moving to stackId=" + stackId + " instead.");
+ // Check that we aren't reparenting to the same stack that the task is already in
+ if (prevStack != null && prevStack.mStackId == stackId) {
+ Slog.w(TAG, "Can not reparent to same stack, task=" + task
+ + " already in stackId=" + stackId);
+ return prevStack;
}
- // Temporarily disable resizeablility of task we are moving. We don't want it to be resized
- // if a docked stack is created below which will lead to the stack we are moving from and
- // its resizeable tasks being resized.
- task.mTemporarilyUnresizable = true;
- final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
- task.mTemporarilyUnresizable = false;
- task.reparent(stack.mStackId, toTop ? MAX_VALUE : 0, reason);
-
- // Reset the resumed activity on the previous stack
- if (wasResumed) {
- prevStack.mResumedActivity = null;
- }
- // Reset the paused activity on the previous stack
- if (wasPaused) {
- prevStack.mPausingActivity = null;
- prevStack.removeTimeoutsForActivityLocked(r);
+ // Ensure that we aren't trying to move into a multi-window stack without multi-window
+ // support
+ if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+ throw new IllegalArgumentException("Device doesn't support multi-window, can not"
+ + " reparent task=" + task + " to stackId=" + stackId);
}
- // If the task had focus before (or we're requested to move focus),
- // move focus to the new stack by moving the stack to the front.
- stack.moveToFrontAndResumeStateIfNeeded(r, forceFocus || wasFocused || wasFront, wasResumed,
- wasPaused, reason);
-
- return stack;
- }
-
- boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
- String reason, boolean animate) {
- return moveTaskToStackLocked(taskId, stackId, toTop, forceFocus, reason, animate,
- false /* deferResume */);
- }
-
- boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
- String reason, boolean animate, boolean deferResume) {
- final TaskRecord task = anyTaskForIdLocked(taskId);
- if (task == null) {
- Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
- return false;
- }
-
- final ActivityStack currentStack = task.getStack();
- if (currentStack != null && currentStack.mStackId == stackId) {
- // You are already in the right stack silly...
- Slog.i(TAG, "moveTaskToStack: taskId=" + taskId + " already in stackId=" + stackId);
- return true;
- }
-
+ // Ensure that we aren't trying to move into a freeform stack without freeform
+ // support
if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
- throw new IllegalArgumentException("moveTaskToStack:"
- + "Attempt to move task " + taskId + " to unsupported freeform stack");
+ throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
+ + " task=" + task);
}
- final ActivityRecord topActivity = task.getTopActivity();
- final int sourceStackId = task.getStackId();
- final boolean mightReplaceWindow =
- StackId.replaceWindowsOnTaskMove(sourceStackId, stackId) && topActivity != null;
- if (mightReplaceWindow) {
- // We are about to relaunch the activity because its configuration changed due to
- // being maximized, i.e. size change. The activity will first remove the old window
- // and then add a new one. This call will tell window manager about this, so it can
- // preserve the old window until the new one is drawn. This prevents having a gap
- // between the removal and addition, in which no window is visible. We also want the
- // entrance of the new window to be properly animated.
- // Note here we always set the replacing window first, as the flags might be needed
- // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
- mWindowManager.setWillReplaceWindow(topActivity.appToken, animate);
+ // We don't allow moving a unresizeable task to the docked stack since the docked stack is
+ // used for split-screen mode and will cause things like the docked divider to show up. We
+ // instead leave the task in its current stack or move it to the fullscreen stack if it
+ // isn't currently in a stack.
+ if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
+ stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+ Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
+ + " Moving to stackId=" + stackId + " instead.");
}
- mWindowManager.deferSurfaceLayout();
- final int preferredLaunchStackId = stackId;
- boolean kept = true;
+ // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
+ // example, a docked stack is created which will lead to the stack we are moving from being
+ // resized and and its resizeable tasks being resized.
try {
- final ActivityStack stack = moveTaskToStackUncheckedLocked(
- task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
- stackId = stack.mStackId;
-
- if (!animate) {
- stack.mNoAnimActivities.add(topActivity);
- }
-
- // We might trigger a configuration change. Save the current task bounds for freezing.
- // TODO: Should this call be moved inside the resize method in WM?
- stack.prepareFreezingTaskBounds();
-
- // Make sure the task has the appropriate bounds/size for the stack it is in.
- if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
- && !Objects.equals(task.mBounds, stack.mBounds)) {
- kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
- deferResume);
- } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
- Rect bounds = task.getLaunchBounds();
- if (bounds == null) {
- stack.layoutTaskInStack(task, null);
- bounds = task.mBounds;
- }
- kept = task.resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
- } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
- kept = task.resize(stack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
- deferResume);
- }
+ task.mTemporarilyUnresizable = true;
+ return getStack(stackId, CREATE_IF_NEEDED, toTop);
} finally {
- mWindowManager.continueSurfaceLayout();
+ task.mTemporarilyUnresizable = false;
}
-
- if (mightReplaceWindow) {
- // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
- // window), we need to clear the replace window settings. Otherwise, we schedule a
- // timeout to remove the old window if the replacing window is not coming in time.
- mWindowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
- }
-
- if (!deferResume) {
-
- // The task might have already been running and its visibility needs to be synchronized with
- // the visibility of the stack / windows.
- ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
- resumeFocusedStackTopActivityLocked();
- }
-
- handleNonResizableTaskIfNeeded(task, preferredLaunchStackId, stackId);
-
- return (preferredLaunchStackId == stackId);
}
boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect bounds) {
@@ -2849,8 +2742,8 @@
void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
boolean moveHomeStackToFront) {
-
mWindowManager.deferSurfaceLayout();
+
// Need to make sure the pinned stack exist so we can resize it below...
final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
@@ -2882,12 +2775,26 @@
// was launched from home so home should be visible behind it.
moveHomeStackToFront(reason);
}
- moveTaskToStackLocked(
- task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
+ // Defer resume until below
+ task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+ DEFER_RESUME, reason);
} else {
// There are multiple activities in the task and moving the top activity should
- // reveal/leave the other activities in their original task
- stack.moveActivityToStack(r);
+ // reveal/leave the other activities in their original task.
+
+ // Currently, we don't support reparenting activities across tasks in two different
+ // stacks, so instead, just create a new task in the same stack, reparent the
+ // activity into that task, and then reparent the whole task to the new stack. This
+ // ensures that all the necessary work to migrate states in the old and new stacks
+ // is also done.
+ final TaskRecord newTask = task.getStack().createTaskRecord(
+ getNextTaskIdForUserLocked(r.userId), r.info, r.intent, null, null, true,
+ r.mActivityType);
+ r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
+
+ // Defer resume until below
+ newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+ DEFER_RESUME, reason);
}
// Reset the state that indicates it can enter PiP while pausing after we've moved it
@@ -4880,9 +4787,8 @@
if (launchStackId != INVALID_STACK_ID) {
if (task.getStackId() != launchStackId) {
- moveTaskToStackLocked(
- taskId, launchStackId, ON_TOP, FORCE_FOCUS, "startActivityFromRecents",
- ANIMATE);
+ task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
+ DEFER_RESUME, "startActivityFromRecents");
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 857241a..2e33c62 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -77,11 +77,16 @@
import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
+import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
import static com.android.server.am.ActivityStackSupervisor.TAG_TASKS;
import static com.android.server.am.EventLogTags.AM_NEW_INTENT;
+import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
+import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+
+import static java.lang.Integer.MAX_VALUE;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -1489,9 +1494,9 @@
if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
// If we want to launch adjacent and mTargetStack is not the computed
// launch stack - move task to top of computed stack.
- mSupervisor.moveTaskToStackLocked(intentActivity.task.taskId,
- launchStack.mStackId, ON_TOP, FORCE_FOCUS, "launchToSide",
- ANIMATE);
+ intentActivity.task.reparent(launchStack.mStackId, ON_TOP,
+ REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+ "launchToSide");
} else {
// TODO: This should be reevaluated in MW v2.
// We choose to move task to front instead of launching it adjacent
@@ -1706,8 +1711,8 @@
if (mTargetStack == null) {
mTargetStack = sourceStack;
} else if (mTargetStack != sourceStack) {
- mSupervisor.moveTaskToStackLocked(sourceTask.taskId, mTargetStack.mStackId,
- ON_TOP, FORCE_FOCUS, "launchToSide", !ANIMATE);
+ sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+ !ANIMATE, DEFER_RESUME, "launchToSide");
}
final TaskRecord topTask = mTargetStack.topTask();
@@ -1773,9 +1778,9 @@
mInTask.updateOverrideConfiguration(mLaunchBounds);
int stackId = mInTask.getLaunchStackId();
if (stackId != mInTask.getStackId()) {
- final ActivityStack stack = mSupervisor.moveTaskToStackUncheckedLocked(mInTask,
- stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
- stackId = stack.mStackId;
+ mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+ DEFER_RESUME, "inTaskToFront");
+ stackId = mInTask.getStackId();
}
if (StackId.resizeStackWithLaunchBounds(stackId)) {
mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 2410424..3f33f41 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,7 @@
package com.android.server.am;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
@@ -54,6 +55,7 @@
import com.android.server.wm.StackWindowController;
import com.android.server.wm.TaskWindowContainerController;
import com.android.server.wm.TaskWindowContainerListener;
+import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,10 +64,13 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Objects;
import static android.app.ActivityManager.RESIZE_MODE_FORCED;
+import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
@@ -107,9 +112,10 @@
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.STARTING_WINDOW_SHOWN;
import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static java.lang.Integer.MAX_VALUE;
+
final class TaskRecord extends ConfigurationContainer implements TaskWindowContainerListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskRecord" : TAG_AM;
private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
@@ -160,6 +166,24 @@
static final int INVALID_TASK_ID = -1;
private static final int INVALID_MIN_SIZE = -1;
+ /**
+ * The modes to control how the stack is moved to the front when calling
+ * {@link TaskRecord#reparent}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ REPARENT_MOVE_STACK_TO_FRONT,
+ REPARENT_KEEP_STACK_AT_FRONT,
+ REPARENT_LEAVE_STACK_IN_PLACE
+ })
+ public @interface ReparentMoveStackMode {}
+ // Moves the stack to the front if it was not at the front
+ public static final int REPARENT_MOVE_STACK_TO_FRONT = 0;
+ // Only moves the stack to the front if it was focused or front most already
+ public static final int REPARENT_KEEP_STACK_AT_FRONT = 1;
+ // Do not move the stack as a part of reparenting
+ public static final int REPARENT_LEAVE_STACK_IN_PLACE = 2;
+
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
String rootAffinity; // Initial base affinity, or null; does not change from initial root.
@@ -537,36 +561,159 @@
mWindowContainerController.getBounds(bounds);
}
- // TODO: Should we be doing all the stuff in ASS.moveTaskToStackLocked?
- void reparent(int stackId, int position, String reason) {
- mService.mWindowManager.deferSurfaceLayout();
+ /**
+ * Convenience method to reparent a task to the top or bottom position of the stack.
+ */
+ boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
+ boolean animate, boolean deferResume, String reason) {
+ return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+ deferResume, reason);
+ }
+ /**
+ * Reparents the task into a preferred stack, creating it if necessary.
+ *
+ * @param preferredStackId the stack id of the target stack to move this task
+ * @param position the position to place this task in the new stack
+ * @param animate whether or not we should wait for the new window created as a part of the
+ * reparenting to be drawn and animated in
+ * @param moveStackMode whether or not to move the stack to the front always, only if it was
+ * previously focused & in front, or never
+ * @param deferResume whether or not to update the visibility of other tasks and stacks that may
+ * have changed as a result of this reparenting
+ * @param reason the caller of this reparenting
+ * @return
+ */
+ boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
+ boolean animate, boolean deferResume, String reason) {
+ final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+ final WindowManagerService windowManager = mService.mWindowManager;
+ final ActivityStack sourceStack = getStack();
+ final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+ position == MAX_VALUE);
+ if (toStack == sourceStack) {
+ return false;
+ }
+
+ final int sourceStackId = getStackId();
+ final int stackId = toStack.getStackId();
+ final ActivityRecord topActivity = getTopActivity();
+
+ final boolean mightReplaceWindow = StackId.replaceWindowsOnTaskMove(sourceStackId, stackId)
+ && topActivity != null;
+ if (mightReplaceWindow) {
+ // We are about to relaunch the activity because its configuration changed due to
+ // being maximized, i.e. size change. The activity will first remove the old window
+ // and then add a new one. This call will tell window manager about this, so it can
+ // preserve the old window until the new one is drawn. This prevents having a gap
+ // between the removal and addition, in which no window is visible. We also want the
+ // entrance of the new window to be properly animated.
+ // Note here we always set the replacing window first, as the flags might be needed
+ // during the relaunch. If we end up not doing any relaunch, we clear the flags later.
+ windowManager.setWillReplaceWindow(topActivity.appToken, animate);
+ }
+
+ windowManager.deferSurfaceLayout();
+ boolean kept = true;
try {
- final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
- final ActivityStack newStack = supervisor.getStack(stackId,
- CREATE_IF_NEEDED, false /* toTop */);
+ final ActivityRecord r = topRunningActivityLocked();
+ final boolean wasFocused = supervisor.isFocusedStack(sourceStack)
+ && (topRunningActivityLocked() == r);
+ final boolean wasResumed = sourceStack.mResumedActivity == r;
+ final boolean wasPaused = sourceStack.mPausingActivity == r;
+
+ // In some cases the focused stack isn't the front stack. E.g. pinned stack.
+ // Whenever we are moving the top activity from the front stack we want to make sure to
+ // move the stack to the front.
+ final boolean wasFront = supervisor.isFrontStackOnDisplay(sourceStack)
+ && (sourceStack.topRunningActivityLocked() == r);
+
// Adjust the position for the new parent stack as needed.
- position = newStack.getAdjustedPositionForTask(this, position, null /* starting */);
+ position = toStack.getAdjustedPositionForTask(this, position, null /* starting */);
// Must reparent first in window manager to avoid a situation where AM can delete the
// we are coming from in WM before we reparent because it became empty.
- mWindowContainerController.reparent(newStack.getWindowContainerController(), position);
+ mWindowContainerController.reparent(toStack.getWindowContainerController(), position);
- final ActivityStack prevStack = mStack;
- prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
- newStack.addTask(this, position, reason);
+ // Reset the resumed activity on the previous stack
+ if (wasResumed) {
+ sourceStack.mResumedActivity = null;
+ }
- supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, prevStack);
+ // Reset the paused activity on the previous stack
+ if (wasPaused) {
+ sourceStack.mPausingActivity = null;
+ sourceStack.removeTimeoutsForActivityLocked(r);
+ }
+ // Move the task
+ sourceStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
+ toStack.addTask(this, position, reason);
+
+ // TODO: Ensure that this is actually necessary here
+ // Notify of picture-in-picture mode changes
+ supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, sourceStack);
+
+ // TODO: Ensure that this is actually necessary here
+ // Notify the voice session if required
if (voiceSession != null) {
try {
voiceSession.taskStarted(intent, taskId);
} catch (RemoteException e) {
}
}
+
+ // If the task had focus before (or we're requested to move focus), move focus to the
+ // new stack by moving the stack to the front.
+ final boolean moveStackToFront = moveStackMode == REPARENT_MOVE_STACK_TO_FRONT
+ || (moveStackMode == REPARENT_KEEP_STACK_AT_FRONT && (wasFocused || wasFront));
+ toStack.moveToFrontAndResumeStateIfNeeded(r, moveStackToFront, wasResumed, wasPaused,
+ reason);
+ if (!animate) {
+ toStack.mNoAnimActivities.add(topActivity);
+ }
+
+ // We might trigger a configuration change. Save the current task bounds for freezing.
+ // TODO: Should this call be moved inside the resize method in WM?
+ toStack.prepareFreezingTaskBounds();
+
+ // Make sure the task has the appropriate bounds/size for the stack it is in.
+ if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
+ && !Objects.equals(mBounds, toStack.mBounds)) {
+ kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ deferResume);
+ } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+ Rect bounds = getLaunchBounds();
+ if (bounds == null) {
+ toStack.layoutTaskInStack(this, null);
+ bounds = mBounds;
+ }
+ kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
+ } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
+ kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+ deferResume);
+ }
} finally {
- mService.mWindowManager.continueSurfaceLayout();
+ windowManager.continueSurfaceLayout();
}
+
+ if (mightReplaceWindow) {
+ // If we didn't actual do a relaunch (indicated by kept==true meaning we kept the old
+ // window), we need to clear the replace window settings. Otherwise, we schedule a
+ // timeout to remove the old window if the replacing window is not coming in time.
+ windowManager.scheduleClearWillReplaceWindows(topActivity.appToken, !kept);
+ }
+
+ if (!deferResume) {
+ // The task might have already been running and its visibility needs to be synchronized
+ // with the visibility of the stack / windows.
+ supervisor.ensureActivitiesVisibleLocked(null, 0, !mightReplaceWindow);
+ supervisor.resumeFocusedStackTopActivityLocked();
+ }
+
+ supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, stackId);
+
+ return (preferredStackId == stackId);
}
void cancelWindowTransition() {