Have better separation between adding, positioning, and reparenting task
Several methods in activity manager and window manager performed adding,
positioning, and reparenting a task operation and sometimes failed silently
when things don't work due the callers using the methods for a particular
operation, but getting a different operation due to programmer error.
This CL better separate the methods responsible for adding, positioning, and
reparenting a task and also fails hard when there is an error.
Test: bit FrameworksServicesTests:com.android.server.wm.TaskWindowContainerControllerTests
Test: Manual testing existing PiP doesn't leave the device in a bad state.
Bug: 34260633
Change-Id: Id64367da30fc6214eb6f95b2bd5e58ed0e953a88
(cherry picked from commit c5cc301689649695e03f502e7d1c1492ef5e5d1e)
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c87412e..7fd91cb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9572,7 +9572,7 @@
boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
if (stackId != task.getStackId()) {
mStackSupervisor.moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
- !FORCE_FOCUS, "resizeTask", true /* allowStackOnTop */);
+ !FORCE_FOCUS, "resizeTask");
preserveWindow = false;
}
@@ -10020,7 +10020,7 @@
// 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 */, true /* allowStackOnTop */);
+ ANIMATE, true /* deferResume */);
final int size = tasks.size();
for (int i = 0; i < size; i++) {
final int id = tasks.get(i).taskId;
@@ -10029,8 +10029,7 @@
}
mStackSupervisor.moveTaskToStackLocked(id,
FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */, !FORCE_FOCUS,
- "swapDockedAndFullscreenStack", ANIMATE, true /* deferResume */,
- true /* allowStackOnTop */);
+ "swapDockedAndFullscreenStack", ANIMATE, true /* deferResume */);
}
// Because we deferred the resume, to avoid conflicts with stack switches while
@@ -10071,7 +10070,7 @@
mWindowManager.setDockedStackCreateState(createMode, initialBounds);
final boolean moved = mStackSupervisor.moveTaskToStackLocked(
taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
- animate, DEFER_RESUME, true /* allowStackOnTop */);
+ animate, DEFER_RESUME);
if (moved) {
if (moveHomeStackFront) {
mStackSupervisor.moveHomeStackToFront("moveTaskToDockedStack");
@@ -10187,10 +10186,24 @@
try {
if (DEBUG_STACK) Slog.d(TAG_STACK, "positionTaskInStack: positioning task="
+ taskId + " in stackId=" + stackId + " at position=" + position);
+ final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+ if (task == null) {
+ throw new IllegalArgumentException("positionTaskInStack: no task for id="
+ + taskId);
+ }
+
final ActivityStack stack = mStackSupervisor.getStack(stackId, CREATE_IF_NEEDED,
!ON_TOP);
- stack.positionChildAt(taskId, position);
+ // TODO: Have the callers of this API call a separate reparent method if that is
+ // what they intended to do vs. having this method also do reparenting.
+ if (task.getStack() == stack) {
+ // Change position in current stack.
+ stack.positionChildAt(task, position);
+ } else {
+ // Reparent to new stack.
+ task.reparent(stackId, position, "positionTaskInStack");
+ }
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index abcaa24..4df0cb1 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -72,6 +72,8 @@
import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN_BEHIND;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
+import static java.lang.Integer.MAX_VALUE;
+import static java.lang.Integer.MIN_VALUE;
import android.app.Activity;
import android.app.ActivityManager;
@@ -2534,6 +2536,30 @@
return null;
}
+ /** Returns the position the input task should be placed in this stack. */
+ int getAdjustedPositionForTask(TaskRecord task, int suggestedPosition,
+ ActivityRecord starting) {
+
+ int maxPosition = mTaskHistory.size();
+ if ((starting != null && starting.okToShowLocked())
+ || (starting == null && task.okToShowLocked())) {
+ // If the task or starting activity can be shown, then whatever position is okay.
+ return Math.min(suggestedPosition, maxPosition);
+ }
+
+ // The task can't be shown, put non-current user tasks below current user tasks.
+ while (maxPosition > 0) {
+ final TaskRecord tmpTask = mTaskHistory.get(maxPosition - 1);
+ if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId)
+ || tmpTask.topRunningActivityLocked() == null) {
+ break;
+ }
+ maxPosition--;
+ }
+
+ return Math.min(suggestedPosition, maxPosition);
+ }
+
/**
* Used from {@link ActivityStack#positionTask(TaskRecord, int)}.
* @see ActivityManagerService#positionTaskInStack(int, int, int).
@@ -2543,32 +2569,25 @@
insertTaskAtTop(task, null);
return;
}
- // Calculate maximum possible position for this task.
- int maxPosition = mTaskHistory.size();
- if (!task.okToShowLocked()) {
- // Put non-current user tasks below current user tasks.
- while (maxPosition > 0) {
- final TaskRecord tmpTask = mTaskHistory.get(maxPosition - 1);
- if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId)
- || tmpTask.topRunningActivityLocked() == null) {
- break;
- }
- maxPosition--;
- }
- }
- position = Math.min(position, maxPosition);
+ position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
mTaskHistory.add(position, task);
- task.positionWindowContainerAt(mStackId, position);
+ task.positionWindowContainerAt(position);
updateTaskMovement(task, true);
}
- private void insertTaskAtTop(TaskRecord task, ActivityRecord newActivity) {
- insertTaskAtTop(task, newActivity, true /* allowStackOnTop */);
+ private void insertTaskAtTop(TaskRecord task, ActivityRecord starting) {
+ updateTaskReturnToForTopInsertion(task);
+ // TODO: Better place to put all the code below...may be addTask...
+ mTaskHistory.remove(task);
+ // Now put task at top.
+ final int position = getAdjustedPositionForTask(task, mTaskHistory.size(), starting);
+ mTaskHistory.add(position, task);
+ updateTaskMovement(task, true);
+ task.moveWindowContainerToTop(true /* includingParents */);
}
- private void insertTaskAtTop(TaskRecord task, ActivityRecord newActivity,
- boolean allowStackOnTop) {
+ private void updateTaskReturnToForTopInsertion(TaskRecord task) {
boolean isLastTaskOverHome = false;
// If the moving task is over home stack, transfer its return type to next task
if (task.isOverHomeStack()) {
@@ -2585,49 +2604,27 @@
if (isOnHomeDisplay()) {
ActivityStack lastStack = mStackSupervisor.getLastStack();
final boolean fromHomeOrRecents = lastStack.isHomeOrRecentsStack();
- final boolean fromOnTopLauncher = lastStack.topTask() != null &&
- lastStack.topTask().isOnTopLauncher();
+ final TaskRecord topTask = lastStack.topTask();
+ final boolean fromOnTopLauncher = topTask != null && topTask.isOnTopLauncher();
if (fromOnTopLauncher) {
// Since an on-top launcher will is moved to back when tasks are launched from it,
// those tasks should first try to return to a non-home activity.
// This also makes sure that non-home activities are visible under a transparent
// non-home activity.
task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
- } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask() != task)) {
+ } else if (!isHomeOrRecentsStack() && (fromHomeOrRecents || topTask != task)) {
// If it's a last task over home - we default to keep its return to type not to
// make underlying task focused when this one will be finished.
int returnToType = isLastTaskOverHome
? task.getTaskToReturnTo() : APPLICATION_ACTIVITY_TYPE;
if (fromHomeOrRecents && StackId.allowTopTaskToReturnHome(mStackId)) {
- returnToType = lastStack.topTask() == null
- ? HOME_ACTIVITY_TYPE : lastStack.topTask().taskType;
+ returnToType = topTask == null ? HOME_ACTIVITY_TYPE : topTask.taskType;
}
task.setTaskToReturnTo(returnToType);
}
} else {
task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
}
-
- mTaskHistory.remove(task);
- // Now put task at top.
- int taskNdx = mTaskHistory.size();
- final boolean notShownWhenLocked =
- (newActivity != null && !newActivity.okToShowLocked())
- || (newActivity == null && !task.okToShowLocked());
- if (notShownWhenLocked) {
- // Put non-current user tasks below current user tasks.
- while (--taskNdx >= 0) {
- final TaskRecord tmpTask = mTaskHistory.get(taskNdx);
- if (!mStackSupervisor.isCurrentProfileLocked(tmpTask.userId)
- || tmpTask.topRunningActivityLocked() == null) {
- break;
- }
- }
- ++taskNdx;
- }
- mTaskHistory.add(taskNdx, task);
- updateTaskMovement(task, true);
- task.moveWindowContainerToTop(allowStackOnTop /* includingParents */);
}
final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
@@ -4891,38 +4888,49 @@
}
void addTask(final TaskRecord task, final boolean toTop, String reason) {
- addTask(task, toTop, reason, true /* allowStackOnTop */);
+ addTask(task, toTop ? MAX_VALUE : 0, reason);
+ if (toTop) {
+ // TODO: figure-out a way to remove this call.
+ task.moveWindowContainerToTop(true /* includingParents */);
+ }
}
- void addTask(final TaskRecord task, final boolean toTop, String reason,
- boolean allowStackOnTop) {
+ // TODO: This shouldn't allow automatic reparenting. Remove the call to preAddTask and deal
+ // with the fall-out...
+ void addTask(final TaskRecord task, int position, String reason) {
+ // TODO: Is this remove really needed? Need to look into the call path for the other addTask
+ mTaskHistory.remove(task);
+ position = getAdjustedPositionForTask(task, position, null /* starting */);
+ final boolean toTop = position >= mTaskHistory.size();
final ActivityStack prevStack = preAddTask(task, reason, toTop);
task.setStack(this);
+
if (toTop) {
- insertTaskAtTop(task, null, allowStackOnTop);
- } else {
- mTaskHistory.add(0, task);
- updateTaskMovement(task, false);
+ updateTaskReturnToForTopInsertion(task);
}
+
+ mTaskHistory.add(position, task);
+ updateTaskMovement(task, toTop);
+
postAddTask(task, prevStack);
}
- void positionChildAt(int taskId, int index) {
- final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
- if (task == null) {
- Slog.w(TAG, "positionTaskInStackLocked: no task for id=" + taskId);
- return;
+ void positionChildAt(TaskRecord task, int index) {
+
+ if (task.getStack() != this) {
+ throw new IllegalArgumentException("AS.positionChildAt: task=" + task
+ + " is not a child of stack=" + this + " current parent=" + task.getStack());
}
task.updateOverrideConfigurationForStack(this);
final ActivityRecord topRunningActivity = task.topRunningActivityLocked();
final boolean wasResumed = topRunningActivity == task.getStack().mResumedActivity;
- final ActivityStack prevStack = preAddTask(task, "positionTask", !ON_TOP);
task.setStack(this);
insertTaskAtPosition(task, index);
- postAddTask(task, prevStack);
+ postAddTask(task, null /* prevStack */);
+
if (wasResumed) {
if (mResumedActivity != null) {
Log.wtf(TAG, "mResumedActivity was already set when moving mResumedActivity from"
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8cf0708..527e918 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1933,7 +1933,7 @@
}
if (stackId != currentStack.mStackId) {
currentStack = moveTaskToStackUncheckedLocked(task, stackId, ON_TOP,
- !FORCE_FOCUS, reason, true /* allowStackOnTop */);
+ !FORCE_FOCUS, reason);
stackId = currentStack.mStackId;
// moveTaskToStackUncheckedLocked() should already placed the task on top,
// still need moveTaskToFrontLocked() below for any transition settings.
@@ -2238,18 +2238,16 @@
}
moveTaskToStackLocked(tasks.get(i).taskId,
FULLSCREEN_WORKSPACE_STACK_ID, onTop, onTop /*forceFocus*/,
- "moveTasksToFullscreenStack - onTop", ANIMATE, DEFER_RESUME,
- true /* allowStackOnTop */);
+ "moveTasksToFullscreenStack - onTop", ANIMATE, DEFER_RESUME);
}
ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
} else {
for (int i = 0; i < size; i++) {
- moveTaskToStackLocked(tasks.get(i).taskId, FULLSCREEN_WORKSPACE_STACK_ID,
- true /* onTop */, false /* forceFocus */,
- "moveTasksToFullscreenStack - NOT_onTop", !ANIMATE, DEFER_RESUME,
- false /* allowStackOnTop */);
+ final TaskRecord task = tasks.get(i);
+ task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, MAX_VALUE,
+ "moveTasksToFullscreenStack - NOT_onTop");
}
}
} finally {
@@ -2385,7 +2383,8 @@
final int insertPosition = isFullscreenStackVisible
? Math.max(0, fullscreenStack.getChildCount() - 1)
: fullscreenStack.getChildCount();
- fullscreenStack.positionChildAt(tasks.get(i).taskId, insertPosition);
+ final TaskRecord task = tasks.get(i);
+ task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, insertPosition, "removeStack");
}
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
@@ -2605,13 +2604,10 @@
* @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.
- * @param allowStackOnTop If stack movement should be moved to the top due to the addition of
- * the task to the stack. E.g. Moving the stack to the front because it
- * should be focused because it now contains the focused activity.
* @return The stack the task was moved to.
*/
ActivityStack moveTaskToStackUncheckedLocked(TaskRecord task, int stackId, boolean toTop,
- boolean forceFocus, String reason, boolean allowStackOnTop) {
+ boolean forceFocus, String reason) {
if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
throw new IllegalStateException("moveTaskToStackUncheckedLocked: Device doesn't "
@@ -2642,15 +2638,14 @@
// 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 && allowStackOnTop);
+ final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
task.mTemporarilyUnresizable = false;
- task.reparentWindowContainer(stack.mStackId, toTop ? MAX_VALUE : MIN_VALUE);
- stack.addTask(task, toTop, reason, allowStackOnTop);
+ task.reparent(stack.mStackId, toTop ? MAX_VALUE : 0, reason);
// 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, allowStackOnTop && (forceFocus || wasFocused || wasFront), wasResumed, reason);
+ r, forceFocus || wasFocused || wasFront, wasResumed, reason);
return stack;
}
@@ -2658,11 +2653,11 @@
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
String reason, boolean animate) {
return moveTaskToStackLocked(taskId, stackId, toTop, forceFocus, reason, animate,
- false /* deferResume */, true /* allowStackOnTop */);
+ false /* deferResume */);
}
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
- String reason, boolean animate, boolean deferResume, boolean allowStackOnTop) {
+ String reason, boolean animate, boolean deferResume) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
Slog.w(TAG, "moveTaskToStack: no task for id=" + taskId);
@@ -2702,7 +2697,7 @@
boolean kept = true;
try {
final ActivityStack stack = moveTaskToStackUncheckedLocked(
- task, stackId, toTop, forceFocus, reason + " moveTaskToStack", allowStackOnTop);
+ task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
stackId = stack.mStackId;
if (!animate) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 547e0b2..81a5d3b 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1777,7 +1777,7 @@
int stackId = mInTask.getLaunchStackId();
if (stackId != mInTask.getStackId()) {
final ActivityStack stack = mSupervisor.moveTaskToStackUncheckedLocked(mInTask,
- stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront", true /* allowStackOnTop */);
+ stackId, ON_TOP, !FORCE_FOCUS, "inTaskToFront");
stackId = stack.mStackId;
}
if (StackId.resizeStackWithLaunchBounds(stackId)) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 9e22c50..4c4c444 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -101,6 +101,8 @@
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.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;
final class TaskRecord extends ConfigurationContainer {
@@ -512,8 +514,8 @@
}
// TODO: Remove once we have a stack controller.
- void positionWindowContainerAt(int stackId, int index) {
- mWindowContainerController.positionAt(stackId, index, mBounds, getOverrideConfiguration());
+ void positionWindowContainerAt(int position) {
+ mWindowContainerController.positionAt(position, mBounds, getOverrideConfiguration());
}
// TODO: Replace with moveChildToTop?
@@ -534,9 +536,36 @@
mWindowContainerController.getBounds(bounds);
}
- // TODO: make this part of adding it to the stack?
- void reparentWindowContainer(int stackId, int position) {
- mWindowContainerController.reparent(stackId, position);
+ // TODO: Should we be doing all the stuff in ASS.moveTaskToStackLocked?
+ void reparent(int stackId, int position, String reason) {
+ mService.mWindowManager.deferSurfaceLayout();
+
+ try {
+ final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
+ final ActivityStack newStack = supervisor.getStack(stackId,
+ CREATE_IF_NEEDED, false /* toTop */);
+ // Adjust the position for the new parent stack as needed.
+ position = newStack.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(stackId, position);
+
+ final ActivityStack prevStack = mStack;
+ prevStack.removeTask(this, reason, REMOVE_TASK_MODE_MOVING);
+ newStack.addTask(this, position, reason);
+
+ supervisor.scheduleReportPictureInPictureModeChangedIfNeeded(this, prevStack);
+
+ if (voiceSession != null) {
+ try {
+ voiceSession.taskStarted(intent, taskId);
+ } catch (RemoteException e) {
+ }
+ }
+ } finally {
+ mService.mWindowManager.continueSurfaceLayout();
+ }
}
void cancelWindowTransition() {
@@ -978,6 +1007,8 @@
addActivityAtIndex(mActivities.size(), r);
}
+ // TODO: Figure-out if any of the call points expect the window container to be reparented and
+ // correct them to use the right method.
void addActivityAtIndex(int index, ActivityRecord r) {
// Remove r first, and if it wasn't already in the list and it's fullscreen, count it.
if (!mActivities.remove(r) && r.fullscreen) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index b1b7542..2d50e3a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -38,6 +38,7 @@
import android.view.DisplayInfo;
import android.view.Surface;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.EventLogTags;
import java.io.PrintWriter;
@@ -104,32 +105,40 @@
}
DisplayContent getDisplayContent() {
- return mStack.getDisplayContent();
+ return mStack != null ? mStack.getDisplayContent() : null;
+ }
+
+ int getAdjustedAddPosition(int suggestedPosition) {
+ final int size = mChildren.size();
+ if (suggestedPosition >= size) {
+ return Math.min(size, suggestedPosition);
+ }
+
+ for (int pos = 0; pos < size && pos < suggestedPosition; ++pos) {
+ // TODO: Confirm that this is the behavior we want long term.
+ if (mChildren.get(pos).removed) {
+ // suggestedPosition assumes removed tokens are actually gone.
+ ++suggestedPosition;
+ }
+ }
+ return Math.min(size, suggestedPosition);
}
@Override
- void addChild(AppWindowToken wtoken, int addPos) {
- final int lastPos = mChildren.size();
- if (addPos >= lastPos) {
- addPos = lastPos;
- } else {
- for (int pos = 0; pos < lastPos && pos < addPos; ++pos) {
- if (mChildren.get(pos).removed) {
- // addPos assumes removed tokens are actually gone.
- ++addPos;
- }
- }
- }
-
- final WindowContainer parent = wtoken.getParent();
- if (parent != null) {
- parent.removeChild(wtoken);
- }
- super.addChild(wtoken, addPos);
+ void addChild(AppWindowToken wtoken, int position) {
+ position = getAdjustedAddPosition(position);
+ super.addChild(wtoken, position);
wtoken.mTask = this;
mDeferRemoval = false;
}
+ @Override
+ void positionChildAt(int position, AppWindowToken child, boolean includingParents) {
+ position = getAdjustedAddPosition(position);
+ super.positionChildAt(position, child, includingParents);
+ mDeferRemoval = false;
+ }
+
private boolean hasWindowsAlive() {
for (int i = mChildren.size() - 1; i >= 0; i--) {
if (mChildren.get(i).hasWindowsAlive()) {
@@ -139,9 +148,14 @@
return false;
}
+ @VisibleForTesting
+ boolean shouldDeferRemoval() {
+ return hasWindowsAlive() && mStack.isAnimating();
+ }
+
@Override
void removeIfPossible() {
- if (hasWindowsAlive() && mStack.isAnimating()) {
+ if (shouldDeferRemoval()) {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: deferring removing taskId=" + mTaskId);
mDeferRemoval = true;
return;
@@ -166,7 +180,8 @@
void reparent(TaskStack stack, int position) {
if (stack == mStack) {
- return;
+ throw new IllegalArgumentException(
+ "task=" + this + " already child of stack=" + mStack);
}
if (DEBUG_STACK) Slog.i(TAG, "reParentTask: removing taskId=" + mTaskId
+ " from stack=" + mStack);
@@ -176,25 +191,8 @@
}
/** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
- void positionTaskInStack(TaskStack stack, int position, Rect bounds,
- Configuration overrideConfig) {
- if (mStack == null) {
- // There is an assumption that task already has a stack at this point, so lets make
- // sure we comply with it.
- throw new IllegalStateException("Trying to position task that has no parent.");
- }
- if (stack != mStack) {
- // Task is already attached to a different stack. First we need to remove it from there
- // and add to top of the target stack. We will move it proper position afterwards.
- if (DEBUG_STACK) Slog.i(TAG, "positionTaskInStack: removing taskId=" + mTaskId
- + " from stack=" + mStack);
- EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "positionTaskInStack");
- mStack.removeChild(this);
- stack.addTask(this, position);
- } else {
- stack.positionChildAt(position, this, true /* includingParents */);
- }
-
+ void positionAt(int position, Rect bounds, Configuration overrideConfig) {
+ mStack.positionChildAt(position, this, false /* includingParents */);
resizeLocked(bounds, overrideConfig, false /* force */);
for (int activityNdx = mChildren.size() - 1; activityNdx >= 0; --activityNdx) {
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 0e4d048..bbc9ed2 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -18,10 +18,10 @@
import android.app.ActivityManager.TaskSnapshot;
import android.content.res.Configuration;
-import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.util.EventLog;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import static com.android.server.EventLogTags.WM_TASK_CREATED;
import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
@@ -59,13 +59,21 @@
+ stackId);
}
EventLog.writeEvent(WM_TASK_CREATED, taskId, stackId);
- final Task task = new Task(taskId, stack, userId, mService, bounds, overrideConfig,
- isOnTopLauncher, resizeMode, homeTask, this);
+ final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
+ homeTask, isOnTopLauncher);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
stack.addTask(task, position, showForAllUsers, true /* moveParents */);
}
}
+ @VisibleForTesting
+ Task createTask(int taskId, TaskStack stack, int userId, Rect bounds,
+ Configuration overrideConfig, int resizeMode, boolean homeTask,
+ boolean isOnTopLauncher) {
+ return new Task(taskId, stack, userId, mService, bounds, overrideConfig, isOnTopLauncher,
+ resizeMode, homeTask, this);
+ }
+
@Override
public void removeContainer() {
synchronized(mWindowMap) {
@@ -78,7 +86,7 @@
}
}
- public void positionChildAt(AppWindowContainerController childController, int index) {
+ public void positionChildAt(AppWindowContainerController childController, int position) {
synchronized(mService.mWindowMap) {
final AppWindowToken aToken = childController.mContainer;
if (aToken == null) {
@@ -91,7 +99,7 @@
if (task == null) {
throw new IllegalArgumentException("positionChildAt: invalid task=" + this);
}
- task.addChild(aToken, index);
+ task.positionChildAt(position, aToken, false /* includeParents */);
}
}
@@ -106,9 +114,7 @@
}
final TaskStack stack = mService.mStackIdToStack.get(stackId);
if (stack == null) {
- if (DEBUG_STACK) Slog.i(TAG_WM,
- "reparent: could not find stackId=" + stackId);
- return;
+ throw new IllegalArgumentException("reparent: could not find stackId=" + stackId);
}
mContainer.reparent(stack, position);
final DisplayContent displayContent = stack.getDisplayContent();
@@ -140,22 +146,22 @@
}
// TODO: Move to positionChildAt() in stack controller once we have a stack controller.
- public void positionAt(int stackId, int index, Rect bounds, Configuration overrideConfig) {
+ public void positionAt(int position, Rect bounds, Configuration overrideConfig) {
synchronized (mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning taskId=" + mTaskId
- + " in stackId=" + stackId + " at " + index);
+ + " at " + position);
if (mContainer == null) {
if (DEBUG_STACK) Slog.i(TAG_WM,
- "positionTaskInStack: could not find taskId=" + mTaskId);
+ "positionAt: could not find taskId=" + mTaskId);
return;
}
- final TaskStack stack = mService.mStackIdToStack.get(stackId);
+ final TaskStack stack = mContainer.mStack;
if (stack == null) {
if (DEBUG_STACK) Slog.i(TAG_WM,
- "positionTaskInStack: could not find stackId=" + stackId);
+ "positionAt: could not find stack for task=" + mContainer);
return;
}
- mContainer.positionTaskInStack(stack, index, bounds, overrideConfig);
+ mContainer.positionAt(position, bounds, overrideConfig);
final DisplayContent displayContent = stack.getDisplayContent();
displayContent.setLayoutNeeded();
mService.mWindowPlacerLocked.performSurfacePlacement();
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index fd7ea6d..e231da8 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -234,13 +234,20 @@
*/
@CallSuper
void positionChildAt(int position, E child, boolean includingParents) {
+
+ if (child.getParent() != this) {
+ throw new IllegalArgumentException("removeChild: container=" + child.getName()
+ + " is not a child of container=" + getName()
+ + " current parent=" + child.getParent());
+ }
+
if ((position < 0 && position != POSITION_BOTTOM)
|| (position > mChildren.size() && position != POSITION_TOP)) {
throw new IllegalArgumentException("positionAt: invalid position=" + position
+ ", children number=" + mChildren.size());
}
- if (position == mChildren.size() - 1) {
+ if (position >= mChildren.size() - 1) {
position = POSITION_TOP;
} else if (position == 0) {
position = POSITION_BOTTOM;
diff --git a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
index 7f1c273..26accc3 100644
--- a/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/AppWindowContainerControllerTests.java
@@ -77,35 +77,8 @@
}
private TestAppWindowContainerController createAppWindowController() {
- final TaskStack stack = createTaskStackOnDisplay(sDisplayContent);
final TestTaskWindowContainerController taskController =
- new TestTaskWindowContainerController(stack.mStackId);
- final IApplicationToken token = new TestIApplicationToken();
- return new TestAppWindowContainerController(taskController, token);
- }
-
- private class TestAppWindowContainerController extends AppWindowContainerController {
-
- final IApplicationToken mToken;
-
- TestAppWindowContainerController(TestTaskWindowContainerController taskController,
- IApplicationToken token) {
- super(taskController, token, null /* listener */, 0 /* index */,
- SCREEN_ORIENTATION_UNSPECIFIED, true /* fullscreen */,
- true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
- false /* launchTaskBehind */, false /* alwaysFocusable */,
- 0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
- 0 /* inputDispatchingTimeoutNanos */, sWm);
- mToken = token;
- }
- }
-
- private class TestIApplicationToken implements IApplicationToken {
-
- private final Binder mBinder = new Binder();
- @Override
- public IBinder asBinder() {
- return mBinder;
- }
+ new TestTaskWindowContainerController();
+ return new TestAppWindowContainerController(taskController);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
index 0dd31c3..f84bf60 100644
--- a/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskWindowContainerControllerTests.java
@@ -22,6 +22,11 @@
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
/**
* Test class for {@link TaskWindowContainerController}.
*
@@ -32,7 +37,73 @@
@Presubmit
@org.junit.runner.RunWith(AndroidJUnit4.class)
public class TaskWindowContainerControllerTests extends WindowTestsBase {
-// TODO Add tests once StackWindowContainerController is created.
+
@Test
- public void dummyTest() throws Exception {}
+ public void testRemoveContainer() throws Exception {
+ final TestTaskWindowContainerController taskController =
+ new TestTaskWindowContainerController();
+ final TestAppWindowContainerController appController =
+ new TestAppWindowContainerController(taskController);
+
+ taskController.removeContainer();
+ // Assert that the container was removed.
+ assertNull(taskController.mContainer);
+ assertNull(appController.mContainer);
+ }
+
+ @Test
+ public void testRemoveContainer_DeferRemoval() throws Exception {
+ final TestTaskWindowContainerController taskController =
+ new TestTaskWindowContainerController();
+ final TestAppWindowContainerController appController =
+ new TestAppWindowContainerController(taskController);
+
+ final TestTask task = (TestTask) taskController.mContainer;
+ final AppWindowToken app = appController.mContainer;
+ task.mShouldDeferRemoval = true;
+
+ taskController.removeContainer();
+ // For the case of deferred removal the task controller will no longer be connected to the
+ // container, but the app controller will still be connected to the its container until
+ // the task window container is removed.
+ assertNull(taskController.mContainer);
+ assertNull(task.getController());
+ assertNotNull(appController.mContainer);
+ assertNotNull(app.getController());
+
+ task.removeImmediately();
+ assertNull(appController.mContainer);
+ assertNull(app.getController());
+ }
+
+ @Test
+ public void testReparent() throws Exception {
+ final TaskStack stack1 = createTaskStackOnDisplay(sDisplayContent);
+ final TestTaskWindowContainerController taskController =
+ new TestTaskWindowContainerController(stack1.mStackId);
+ final TaskStack stack2 = createTaskStackOnDisplay(sDisplayContent);
+ final TestTaskWindowContainerController taskController2 =
+ new TestTaskWindowContainerController(stack2.mStackId);
+
+ boolean gotException = false;
+ try {
+ taskController.reparent(stack1.mStackId, 0);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue("Should not be able to reparent to the same parent", gotException);
+
+ gotException = false;
+ try {
+ taskController.reparent(sNextStackId + 1, 0);
+ } catch (IllegalArgumentException e) {
+ gotException = true;
+ }
+ assertTrue("Should not be able to reparent to a stackId that doesn't exist", gotException);
+
+ taskController.reparent(stack2.mStackId, 0);
+ assertEquals(stack2, taskController.mContainer.getParent());
+ assertEquals(0, ((TestTask) taskController.mContainer).positionInParent());
+ assertEquals(1, ((TestTask) taskController2.mContainer).positionInParent());
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
index b57329c..4f740ac 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTests.java
@@ -315,7 +315,7 @@
gotException = false;
try {
// Check response to position that's bigger than child number.
- root.positionChildAt(2, child1, false /* includingParents */);
+ root.positionChildAt(3, child1, false /* includingParents */);
} catch (IllegalArgumentException e) {
gotException = true;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
index 466bd67..53e5a2a 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestsBase.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.view.IApplicationToken;
import org.junit.Assert;
import org.junit.Before;
@@ -28,6 +32,7 @@
import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
import static android.app.AppOpsManager.OP_NONE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.EMPTY;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
@@ -50,7 +55,7 @@
static WindowManagerService sWm = null;
private final IWindow mIWindow = new TestIWindow();
private final Session mMockSession = mock(Session.class);
- private static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
+ static int sNextStackId = FIRST_DYNAMIC_STACK_ID;
private static int sNextTaskId = 0;
private static boolean sOneTimeSetupDone = false;
@@ -201,16 +206,78 @@
}
}
+ /* Used so we can gain access to some protected members of the {@link Task} class */
+ class TestTask extends Task {
+
+ boolean mShouldDeferRemoval = false;
+
+ TestTask(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
+ Configuration overrideConfig, boolean isOnTopLauncher, int resizeMode,
+ boolean homeTask, TaskWindowContainerController controller) {
+ super(taskId, stack, userId, service, bounds, overrideConfig, isOnTopLauncher,
+ resizeMode, homeTask, controller);
+ }
+
+ boolean shouldDeferRemoval() {
+ return mShouldDeferRemoval;
+ }
+
+ int positionInParent() {
+ return getParent().mChildren.indexOf(this);
+ }
+ }
+
/**
* Used so we can gain access to some protected members of {@link TaskWindowContainerController}
* class.
*/
class TestTaskWindowContainerController extends TaskWindowContainerController {
+ TestTaskWindowContainerController() {
+ this(createTaskStackOnDisplay(sDisplayContent).mStackId);
+ }
+
TestTaskWindowContainerController(int stackId) {
super(sNextTaskId++, stackId, 0 /* userId */, null /* bounds */,
EMPTY /* overrideConfig*/, RESIZE_MODE_UNRESIZEABLE, false /* homeTask*/,
false /* isOnTopLauncher */, true /* toTop*/, true /* showForAllUsers */);
}
+
+ @Override
+ TestTask createTask(int taskId, TaskStack stack, int userId, Rect bounds,
+ Configuration overrideConfig, int resizeMode, boolean homeTask,
+ boolean isOnTopLauncher) {
+ return new TestTask(taskId, stack, userId, mService, bounds, overrideConfig,
+ isOnTopLauncher, resizeMode, homeTask, this);
+ }
+ }
+
+ class TestAppWindowContainerController extends AppWindowContainerController {
+
+ final IApplicationToken mToken;
+
+ TestAppWindowContainerController(TestTaskWindowContainerController taskController) {
+ this(taskController, new TestIApplicationToken());
+ }
+
+ TestAppWindowContainerController(TestTaskWindowContainerController taskController,
+ IApplicationToken token) {
+ super(taskController, token, null /* listener */, 0 /* index */,
+ SCREEN_ORIENTATION_UNSPECIFIED, true /* fullscreen */,
+ true /* showForAllUsers */, 0 /* configChanges */, false /* voiceInteraction */,
+ false /* launchTaskBehind */, false /* alwaysFocusable */,
+ 0 /* targetSdkVersion */, 0 /* rotationAnimationHint */,
+ 0 /* inputDispatchingTimeoutNanos */, sWm);
+ mToken = token;
+ }
+ }
+
+ class TestIApplicationToken implements IApplicationToken {
+
+ private final Binder mBinder = new Binder();
+ @Override
+ public IBinder asBinder() {
+ return mBinder;
+ }
}
}