Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java +56 −20 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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. Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java +120 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/FocusTransitionObserver.java +56 −20 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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. Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/FocusTransitionObserverTest.java +120 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading