Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3be9c5a2 authored by Kazuki Takise's avatar Kazuki Takise
Browse files

Add multi-display support to focus switching in shell transitions

With [1], a transition is created when a task is moved to front
even without any visibility change. This change extends this to
multi-display scenarios.

Task focus is managed per display, which means that, when a top
task on a non-top display is clicked, there's no task focus switch,
(but only display focus switch). The current transition system
doesn't track dipslay focus, so in such cases no transition
happens, and there's no way for Shell to know when display focus
is switched.

With this change, top display is tracked in the same way as top
tasks, so a TO_TOP transition is delivered to Shell even when
there's no task switch inside a display is involved.

[1] If21d076eed4db88139ffc8a7c4c018c2ef5aad93

Bug: 274696524
Test: TransitionTests
Flag: com.android.window.flags.enable_display_focus_in_shell_transitions
Change-Id: I3f74a169eaea5d71a69132a4aa5693fd27bc372c
parent b8242c06
Loading
Loading
Loading
Loading
+31 −5
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_W
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;

import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -222,6 +223,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
     */
    private final ArrayList<Task> mOnTopTasksAtReady = new ArrayList<>();

    /**
     * Tracks the top display like top tasks so we can trigger a MOVED_TO_TOP transition even when
     * a display gets moved to front but there's no change in per-display focused tasks.
     */
    private DisplayContent mOnTopDisplayStart = null;
    private DisplayContent mOnTopDisplayAtReady = null;

    /**
     * Set of participating windowtokens (activity/wallpaper) which are visible at the end of
     * the transition animation.
@@ -772,6 +780,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        if (dc == null || mTargetDisplays.contains(dc)) return;
        mTargetDisplays.add(dc);
        addOnTopTasks(dc, mOnTopTasksStart);
        if (mOnTopDisplayStart == null) {
            mOnTopDisplayStart =
                    mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent();
        }
        // Handle the case {transition.start(); applyTransaction(wct);} that the animating state
        // is set before collecting participants.
        if (mController.isAnimating()) {
@@ -998,6 +1010,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            for (int i = 0; i < mTargetDisplays.size(); ++i) {
                addOnTopTasks(mTargetDisplays.get(i), mOnTopTasksAtReady);
            }
            mOnTopDisplayAtReady =
                    mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent();
            mController.onTransitionPopulated(this);
        }
    }
@@ -2082,6 +2096,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
                return true;
            }
        }
        if (enableDisplayFocusInShellTransitions() && mOnTopDisplayStart
                != mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent()) {
            return true;
        }
        return false;
    }

@@ -2113,6 +2131,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            includesOrderChange = true;
            break;
        }
        includesOrderChange |= enableDisplayFocusInShellTransitions()
                && mOnTopDisplayStart != mOnTopDisplayAtReady;
        if (!includesOrderChange && !reportCurrent) {
            // This transition doesn't include an order change, so if it isn't required to report
            // the current focus (eg. it's the last of a cluster of transitions), then don't
@@ -2123,6 +2143,8 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
        // latest state and compare with the last reported state (or our start state if no
        // reported state exists).
        ArrayList<Task> onTopTasksEnd = new ArrayList<>();
        final DisplayContent onTopDisplayEnd =
                mController.mAtm.mRootWindowContainer.getTopFocusedDisplayContent();
        for (int d = 0; d < mTargetDisplays.size(); ++d) {
            addOnTopTasks(mTargetDisplays.get(d), onTopTasksEnd);
            final int displayId = mTargetDisplays.get(d).mDisplayId;
@@ -2130,12 +2152,16 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
            for (int i = onTopTasksEnd.size() - 1; i >= 0; --i) {
                final Task task = onTopTasksEnd.get(i);
                if (task.getDisplayId() != displayId) continue;
                if (!enableDisplayFocusInShellTransitions()
                        || mOnTopDisplayStart == onTopDisplayEnd
                        || displayId != onTopDisplayEnd.mDisplayId) {
                    // If it didn't change since last report, don't report
                    if (reportedOnTop == null) {
                        if (mOnTopTasksStart.contains(task)) continue;
                    } else if (reportedOnTop.contains(task)) {
                        continue;
                    }
                }
                // Need to report it.
                mParticipants.add(task);
                int changeIdx = mChanges.indexOfKey(task);
+21 −0
Original line number Diff line number Diff line
@@ -90,6 +90,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
@@ -158,6 +159,7 @@ import static com.android.server.wm.WindowManagerServiceDumpProto.ROOT_WINDOW_CO
import static com.android.server.wm.WindowManagerServiceDumpProto.WINDOW_FRAMES_VALID;
import static com.android.window.flags.Flags.multiCrop;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import static com.android.window.flags.Flags.enableDisplayFocusInShellTransitions;

import android.Manifest;
import android.Manifest.permission;
@@ -3238,9 +3240,28 @@ public class WindowManagerService extends IWindowManager.Stub
                    return;
                }

                Transition transition = null;
                boolean transitionNewlyCreated = false;
                if (enableDisplayFocusInShellTransitions()) {
                    transition = mAtmService.getTransitionController().requestTransitionIfNeeded(
                                    TRANSIT_TO_FRONT, 0 /* flags */, null /* trigger */,
                                    displayContent);
                    if (transition != null) {
                        transitionNewlyCreated = true;
                    } else {
                        transition =
                                mAtmService.getTransitionController().getCollectingTransition();
                    }
                    if (transition != null) {
                        transition.recordTaskOrder(displayContent);
                    }
                }
                // Nothing prevented us from moving the display to the top. Let's do it!
                displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
                        displayContent, true /* includingParents */);
                if (transitionNewlyCreated) {
                    transition.setReady(displayContent, true /* ready */);
                }
            }
        }
    }
+52 −3
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.window.flags.Flags.explicitRefreshRateHints;

import static org.junit.Assert.assertEquals;
@@ -140,8 +141,7 @@ public class TransitionTests extends WindowTestsBase {
    }

    private Transition createTestTransition(int transitType) {
        final TransitionController controller = new TestTransitionController(
                mock(ActivityTaskManagerService.class));
        final TransitionController controller = new TestTransitionController(mAtm);

        mSyncEngine = createTestBLASTSyncEngine();
        controller.setSyncEngine(mSyncEngine);
@@ -2358,7 +2358,7 @@ public class TransitionTests extends WindowTestsBase {
    }

    @Test
    public void testMoveToTopWhileVisible() {
    public void testMoveTaskToTopWhileVisible() {
        final Transition transition = createTestTransition(TRANSIT_OPEN);
        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
        final ArraySet<WindowContainer> participants = transition.mParticipants;
@@ -2393,6 +2393,55 @@ public class TransitionTests extends WindowTestsBase {
        assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS)
    public void testMoveDisplayToTop() {
        // Set up two displays, each of which has a task.
        DisplayContent otherDisplay = createNewDisplay();
        final Consumer<DisplayContent> setUpTask = (DisplayContent dc) -> {
            final Task task = createTask(dc);
            final ActivityRecord act = createActivityRecord(task);
            final TestWindowState win = createWindowState(
                    new WindowManager.LayoutParams(TYPE_BASE_APPLICATION), act);
            act.addWindow(win);
            act.setVisibleRequested(true);
        };
        setUpTask.accept(mDisplayContent);
        setUpTask.accept(otherDisplay);
        mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /* updateImWindows */);

        final Transition transition = createTestTransition(TRANSIT_OPEN);
        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
        final ArraySet<WindowContainer> participants = transition.mParticipants;

        // Emulate WindowManagerService#moveDisplayToTopInternal().
        transition.recordTaskOrder(mDefaultDisplay);
        mDefaultDisplay.getParent().positionChildAt(WindowContainer.POSITION_TOP,
                mDefaultDisplay, true /* includingParents */);
        mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /* updateImWindows */);
        transition.setReady(mDefaultDisplay, true /* ready */);

        // Test has order changes, a shallow check of order changes.
        assertTrue(transition.hasOrderChanges());

        // We just moved a display to top, so there shouldn't be any changes.
        ArrayList<Transition.ChangeInfo> targets = Transition.calculateTargets(
                participants, changes);
        assertTrue(targets.isEmpty());

        // After collecting order changes, the task on the newly focused display should be
        // considered to get moved to top.
        transition.collectOrderChanges(true);
        targets = Transition.calculateTargets(participants, changes);
        assertEquals(1, targets.size());

        // Make sure the flag is set
        final TransitionInfo info = Transition.calculateTransitionInfo(
                transition.mType, 0 /* flags */, targets, mMockT);
        assertTrue((info.getChanges().get(0).getFlags() & TransitionInfo.FLAG_MOVED_TO_TOP) != 0);
        assertEquals(TRANSIT_CHANGE, info.getChanges().get(0).getMode());
    }

    private class OrderChangeTestSetup {
        final TransitionController mController;
        final TestTransitionPlayer mPlayer;