From 046523ce96014369cf5052ae6c47128b83174edb Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Tue, 2 Jun 2020 12:28:26 +0800 Subject: [PATCH] Moving all hierarchy chain to top when adjust focusable task. Find a issue that when a background app crashed, the top focus task would also changed because of AMS trigger resumeTopActivities. However there should be nothing changed. For some tasks like home or split screen, because they can be more than one layer of task-to-container structure, so when adjusting focus task, we should move the whole chain to the top of it's container. Also update preferred top focusable stack in TaskDisplayArea#positionChildAt so the API can be more identical. Fixes: 157393169 Test: atest ActivityRecordTests Test: atest ActivityLifecycleTopResumedStateTests Test: atest MultiDisplayPolicyTests Change-Id: Ibf86f375ac91a50a013cfa7a7ea19e571a0a0d61 --- .../com/android/server/wm/ActivityStack.java | 2 +- .../core/java/com/android/server/wm/Task.java | 19 ++++++--- .../android/server/wm/TaskDisplayArea.java | 41 ++++++++++--------- .../server/wm/ActivityRecordTests.java | 33 +++++++++++++++ 4 files changed, 68 insertions(+), 27 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 4bede4c3623f6..b4bc0f5b3a32d 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -813,7 +813,7 @@ class ActivityStack extends Task { /** Resume next focusable stack after reparenting to another display. */ void postReparent() { adjustFocusToNextFocusableTask("reparent", true /* allowFocusSelf */, - true /* moveParentsToTop */); + true /* moveDisplayToTop */); mRootWindowContainer.resumeFocusedStacksTopActivities(); // Update visibility of activities before notifying WM. This way it won't try to resize // windows that are no longer visible. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 56ad9643e619e..0ead575051a1e 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2638,7 +2638,7 @@ class Task extends WindowContainer { */ ActivityStack adjustFocusToNextFocusableTask(String reason) { return adjustFocusToNextFocusableTask(reason, false /* allowFocusSelf */, - true /* moveParentsToTop */); + true /* moveDisplayToTop */); } /** Return the next focusable task by looking from the siblings and parent tasks */ @@ -2661,11 +2661,11 @@ class Task extends WindowContainer { * Find next proper focusable task and make it focused. * @param reason The reason of making the adjustment. * @param allowFocusSelf Is the focus allowed to remain on the same task. - * @param moveParentsToTop Whether to move parents to top while making the task focused. + * @param moveDisplayToTop Whether to move display to top while making the task focused. * @return The root task that now got the focus, {@code null} if none found. */ ActivityStack adjustFocusToNextFocusableTask(String reason, boolean allowFocusSelf, - boolean moveParentsToTop) { + boolean moveDisplayToTop) { ActivityStack focusableTask = (ActivityStack) getNextFocusableTask(allowFocusSelf); if (focusableTask == null) { focusableTask = mRootWindowContainer.getNextFocusableStack((ActivityStack) this, @@ -2676,10 +2676,17 @@ class Task extends WindowContainer { } final ActivityStack rootTask = (ActivityStack) focusableTask.getRootTask(); - if (!moveParentsToTop) { - // Only move the next stack to top in its task container. + if (!moveDisplayToTop) { + // There may be multiple task layers above this task, so when relocating the task to the + // top, we should move this task and each of its parent task that below display area to + // the top of each layer. WindowContainer parent = focusableTask.getParent(); - parent.positionChildAt(POSITION_TOP, focusableTask, false /* includingParents */); + WindowContainer next = focusableTask; + do { + parent.positionChildAt(POSITION_TOP, next, false /* includingParents */); + next = parent; + parent = next.getParent(); + } while (next.asTask() != null && parent != null); return rootTask; } diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 102c2a6364f4e..fbb2b7b3dccab 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -299,8 +299,17 @@ final class TaskDisplayArea extends DisplayArea { @Override void positionChildAt(int position, ActivityStack child, boolean includingParents) { - final boolean moveToTop = (position == POSITION_TOP || position == getChildCount()); + final boolean moveToTop = position >= getChildCount() - 1; final boolean moveToBottom = (position == POSITION_BOTTOM || position == 0); + + // Reset mPreferredTopFocusableStack before positioning to top or {@link + // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top + // resumed activity. + final boolean wasContained = mChildren.contains(child); + if (moveToTop && wasContained && child.isFocusable()) { + mPreferredTopFocusableStack = null; + } + if (child.getWindowConfiguration().isAlwaysOnTop() && !moveToTop) { // This stack is always-on-top, override the default behavior. Slog.w(TAG_WM, "Ignoring move of always-on-top stack=" + this + " to bottom"); @@ -330,6 +339,17 @@ final class TaskDisplayArea extends DisplayArea { child.updateTaskMovement(moveToTop); mDisplayContent.setLayoutNeeded(); + + // The insert position may be adjusted to non-top when there is always-on-top stack. Since + // the original position is preferred to be top, the stack should have higher priority when + // we are looking for top focusable stack. The condition {@code wasContained} restricts the + // preferred stack is set only when moving an existing stack to top instead of adding a new + // stack that may be too early (e.g. in the middle of launching or reparenting). + if (moveToTop && child.isFocusableAndVisible()) { + mPreferredTopFocusableStack = child; + } else if (mPreferredTopFocusableStack == child) { + mPreferredTopFocusableStack = null; + } } /** @@ -727,29 +747,10 @@ final class TaskDisplayArea extends DisplayArea { "positionStackAt: Can only have one task on display=" + this); } - final boolean movingToTop = wasContained && position >= getStackCount() - 1; - // Reset mPreferredTopFocusableStack before positioning to top or {@link - // ActivityStackSupervisor#updateTopResumedActivityIfNeeded()} won't update the top - // resumed activity. - if (movingToTop && stack.isFocusable()) { - mPreferredTopFocusableStack = null; - } - // Since positionChildAt() is called during the creation process of pinned stacks, // ActivityStack#getStack() can be null. positionStackAt(position, stack, includingParents); - // The insert position may be adjusted to non-top when there is always-on-top stack. Since - // the original position is preferred to be top, the stack should have higher priority when - // we are looking for top focusable stack. The condition {@code wasContained} restricts the - // preferred stack is set only when moving an existing stack to top instead of adding a new - // stack that may be too early (e.g. in the middle of launching or reparenting). - if (movingToTop && stack.isFocusableAndVisible()) { - mPreferredTopFocusableStack = stack; - } else if (mPreferredTopFocusableStack == stack) { - mPreferredTopFocusableStack = null; - } - if (updateLastFocusedStackReason != null) { final ActivityStack currentFocusedStack = getFocusedStack(); if (currentFocusedStack != prevFocusedStack) { diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 4ee933a0a5a55..29b96ebdc0904 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -793,6 +793,39 @@ public class ActivityRecordTests extends ActivityTestsBase { assertEquals(mTask, mStack.getTopMostTask()); } + /** + * Verify that when top focused activity is on secondary display, when finishing the top focused + * activity on default display, the preferred top stack on default display should be changed by + * adjusting focus. + */ + @Test + public void testFinishActivityIfPossible_PreferredTopStackChanged() { + final ActivityRecord topActivityOnNonTopDisplay = + createActivityOnDisplay(true /* defaultDisplay */, null /* process */); + ActivityStack topRootableTask = topActivityOnNonTopDisplay.getRootTask(); + topRootableTask.moveToFront("test"); + assertTrue(topRootableTask.isTopStackInDisplayArea()); + assertEquals(topRootableTask, topActivityOnNonTopDisplay.getDisplayArea() + .mPreferredTopFocusableStack); + + final ActivityRecord secondaryDisplayActivity = + createActivityOnDisplay(false /* defaultDisplay */, null /* process */); + topRootableTask = secondaryDisplayActivity.getRootTask(); + topRootableTask.moveToFront("test"); + assertTrue(topRootableTask.isTopStackInDisplayArea()); + assertEquals(topRootableTask, + secondaryDisplayActivity.getDisplayArea().mPreferredTopFocusableStack); + + // The global top focus activity is on secondary display now. + // Finish top activity on default display and verify the next preferred top focusable stack + // on default display has changed. + topActivityOnNonTopDisplay.setState(RESUMED, "test"); + topActivityOnNonTopDisplay.finishIfPossible(0 /* resultCode */, null /* resultData */, + null /* resultGrants */, "test", false /* oomAdj */); + assertEquals(mTask, mStack.getTopMostTask()); + assertEquals(mStack, mActivity.getDisplayArea().mPreferredTopFocusableStack); + } + /** * Verify that resumed activity is paused due to finish request. */