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

Commit 2a9b7695 authored by Saho Kobayashi's avatar Saho Kobayashi
Browse files

Update focus on focused task move to another display

See go/focus-transition-reparent for more detail.

Bug: 383664557
Test: FocusTransitionObserverTest
Flag: com.android.window.flags.enable_move_to_next_display_shortcut
Change-Id: Ic8e2fb8a6e4af8efe95784c08946a8ca7d3cead8
parent ae9c64be
Loading
Loading
Loading
Loading
+56 −20
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
package com.android.wm.shell.transition;

import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
@@ -66,30 +68,69 @@ public class FocusTransitionObserver {
        if (!enableDisplayFocusInShellTransitions()) {
            return;
        }
        final SparseArray<RunningTaskInfo> lastTransitionFocusedTasks =
                mFocusedTaskOnDisplay.clone();

        final List<TransitionInfo.Change> changes = info.getChanges();
        for (int i = changes.size() - 1; i >= 0; i--) {
            final TransitionInfo.Change change = changes.get(i);

            final RunningTaskInfo task = change.getTaskInfo();
            if (task != null
                    && (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN)) {
            if (task != null) {
                if (change.hasFlags(FLAG_MOVED_TO_TOP) || change.getMode() == TRANSIT_OPEN) {
                    updateFocusedTaskPerDisplay(task, task.displayId);
                } else {
                    // Update focus assuming that any task moved to another display is focused in
                    // the new display.
                    // TODO(sahok): remove this logic when b/388665104 is fixed
                    final boolean isBeyondDisplay = change.getStartDisplayId() != INVALID_DISPLAY
                            && change.getEndDisplayId() != INVALID_DISPLAY
                            && change.getStartDisplayId() != change.getEndDisplayId();

                    RunningTaskInfo lastTransitionFocusedTaskOnStartDisplay =
                            lastTransitionFocusedTasks.get(change.getStartDisplayId());
                    final boolean isLastTransitionFocused =
                            lastTransitionFocusedTaskOnStartDisplay != null
                                    && task.taskId
                                            == lastTransitionFocusedTaskOnStartDisplay.taskId;
                    if (change.getMode() == TRANSIT_CHANGE && isBeyondDisplay
                            && isLastTransitionFocused) {
                        // The task have moved to another display and keeps its focus.
                        // MOVE_TO_TOP is not reported but we need to update the focused task in
                        // the end display.
                        updateFocusedTaskPerDisplay(task, change.getEndDisplayId());
                    }
                }
            }


            if (change.hasFlags(FLAG_IS_DISPLAY) && change.hasFlags(FLAG_MOVED_TO_TOP)) {
                if (mFocusedDisplayId != change.getEndDisplayId()) {
                    updateFocusedDisplay(change.getEndDisplayId());
                }
            }
        }
        mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged);
        mTmpTasksToBeNotified.clear();
    }

    private void updateFocusedTaskPerDisplay(RunningTaskInfo task, int displayId) {
        final RunningTaskInfo lastFocusedTaskOnDisplay =
                        mFocusedTaskOnDisplay.get(task.displayId);
                mFocusedTaskOnDisplay.get(displayId);
        if (lastFocusedTaskOnDisplay != null) {
            mTmpTasksToBeNotified.add(lastFocusedTaskOnDisplay);
        }
        mTmpTasksToBeNotified.add(task);
                mFocusedTaskOnDisplay.put(task.displayId, task);
        mFocusedTaskOnDisplay.put(displayId, task);
    }

            if (change.hasFlags(FLAG_IS_DISPLAY) && change.hasFlags(FLAG_MOVED_TO_TOP)) {
                if (mFocusedDisplayId != change.getEndDisplayId()) {
    private void updateFocusedDisplay(int endDisplayId) {
        final RunningTaskInfo lastGloballyFocusedTask =
                mFocusedTaskOnDisplay.get(mFocusedDisplayId);
        if (lastGloballyFocusedTask != null) {
            mTmpTasksToBeNotified.add(lastGloballyFocusedTask);
        }
                    mFocusedDisplayId = change.getEndDisplayId();
        mFocusedDisplayId = endDisplayId;
        notifyFocusedDisplayChanged();
        final RunningTaskInfo currentGloballyFocusedTask =
                mFocusedTaskOnDisplay.get(mFocusedDisplayId);
@@ -97,11 +138,6 @@ public class FocusTransitionObserver {
            mTmpTasksToBeNotified.add(currentGloballyFocusedTask);
        }
    }
            }
        }
        mTmpTasksToBeNotified.forEach(this::notifyTaskFocusChanged);
        mTmpTasksToBeNotified.clear();
    }

    /**
     * Sets the focus transition listener that receives any transitions resulting in focus switch.
+120 −1
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.wm.shell.transition;

import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;

@@ -127,19 +129,136 @@ public class FocusTransitionObserverTest extends ShellTestCase {
                true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
    }

    @Test
    public void testTaskFocusSwitch() throws RemoteException {
        final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);

        // Open 2 tasks on the default display.
        TransitionInfo info = mock(TransitionInfo.class);
        final List<TransitionInfo.Change> changes = new ArrayList<>();
        setupTaskChange(changes, 1 /* taskId */, TRANSIT_OPEN,
                DEFAULT_DISPLAY, true /* focused */);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, never()).onFocusedDisplayChanged(anyInt());
        verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        clearInvocations(mListener);
        changes.clear();

        setupTaskChange(changes, 2 /* taskId */, TRANSIT_OPEN,
                DEFAULT_DISPLAY, true /* focused */);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
                false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
        verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        clearInvocations(mListener);
        changes.clear();

        // Moving a task to front.
        changes.clear();
        setupTaskChange(changes, 1 /* taskId */, TRANSIT_TO_FRONT,
                DEFAULT_DISPLAY, true /* focused */);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
                false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
    }


    @Test
    public void testTaskMoveToAnotherDisplay() throws RemoteException {
        final SurfaceControl.Transaction tx = mock(SurfaceControl.Transaction.class);

        // First, open a task on the default display.
        TransitionInfo info = mock(TransitionInfo.class);
        final List<TransitionInfo.Change> changes = new ArrayList<>();
        setupTaskChange(changes, 1 /* taskId */, TRANSIT_OPEN,
                DEFAULT_DISPLAY, true /* focused */);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, never()).onFocusedDisplayChanged(anyInt());
        verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        clearInvocations(mListener);
        changes.clear();

        // Open 2 tasks on the secondary display.
        setupTaskChange(changes, 2 /* taskId */, TRANSIT_OPEN,
                SECONDARY_DISPLAY_ID, true /* focused */);
        setupDisplayToTopChange(changes, SECONDARY_DISPLAY_ID);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, times(1))
                .onFocusedDisplayChanged(SECONDARY_DISPLAY_ID);
        verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
                true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
        verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        clearInvocations(mListener);
        changes.clear();

        setupTaskChange(changes, 3 /* taskId */, TRANSIT_OPEN,
                SECONDARY_DISPLAY_ID, true /* focused */);
        setupDisplayToTopChange(changes, SECONDARY_DISPLAY_ID);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
                false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
        verify(mListener, times(1)).onFocusedTaskChanged(3 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        clearInvocations(mListener);
        changes.clear();

        // Move focused task in the secondary display to the default display
        setupTaskChange(changes, 3 /* taskId */, TRANSIT_CHANGE,
                SECONDARY_DISPLAY_ID, DEFAULT_DISPLAY, true /* focused */);
        setupTaskChange(changes, 2 /* taskId */, TRANSIT_TO_FRONT,
                SECONDARY_DISPLAY_ID, true /* focused */);
        setupDisplayToTopChange(changes, DEFAULT_DISPLAY);
        when(info.getChanges()).thenReturn(changes);
        mFocusTransitionObserver.updateFocusState(info);
        mShellExecutor.flushAll();
        verify(mListener, times(1)).onFocusedTaskChanged(1 /* taskId */,
                false /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
        verify(mListener, times(1)).onFocusedTaskChanged(2 /* taskId */,
                true /* isFocusedOnDisplay */, false /* isFocusedGlobally */);
        verify(mListener, times(1)).onFocusedTaskChanged(3 /* taskId */,
                true /* isFocusedOnDisplay */, true /* isFocusedGlobally */);
        clearInvocations(mListener);
    }

    private void setupTaskChange(List<TransitionInfo.Change> changes, int taskId,
            @TransitionMode int mode, int displayId, boolean focused) {
        setupTaskChange(changes, taskId, mode, displayId, displayId, focused);
    }

    private void setupTaskChange(List<TransitionInfo.Change> changes, int taskId,
            @TransitionMode int mode, int startDisplayId, int endDisplayId, boolean focused) {
        TransitionInfo.Change change = mock(TransitionInfo.Change.class);
        RunningTaskInfo taskInfo = mock(RunningTaskInfo.class);
        taskInfo.taskId = taskId;
        taskInfo.isFocused = focused;
        when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(focused);
        taskInfo.displayId = displayId;
        taskInfo.displayId = endDisplayId;
        when(change.getStartDisplayId()).thenReturn(startDisplayId);
        when(change.getEndDisplayId()).thenReturn(endDisplayId);
        when(change.getTaskInfo()).thenReturn(taskInfo);
        when(change.getMode()).thenReturn(mode);
        changes.add(change);
    }


    private void setupDisplayToTopChange(List<TransitionInfo.Change> changes, int displayId) {
        TransitionInfo.Change change = mock(TransitionInfo.Change.class);
        when(change.hasFlags(FLAG_MOVED_TO_TOP)).thenReturn(true);