Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.kt +12 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.splitscreen import android.window.WindowContainerToken import android.window.WindowContainerTransaction interface SplitMultiDisplayProvider { /** Loading @@ -26,4 +27,15 @@ interface SplitMultiDisplayProvider { * @return The {@link WindowContainerToken} associated with the display's root task. */ fun getDisplayRootForDisplayId(displayId: Int): WindowContainerToken? /** * Prepares to reparent the split-screen root to another display if the target task * resides on a different display. This is used to move the entire split-screen container * to the display where the user interaction is occurring. It only adds the reparent * operation to the given {@code wct} without executing it. * * @param wct The transaction to add the reparent operation to. * @param displayId The ID of the target display. */ fun prepareMovingSplitScreenRoot(wct: WindowContainerTransaction?, displayId: Int) } libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +46 −11 Original line number Diff line number Diff line Loading @@ -320,6 +320,41 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return rootTaskInfo != null ? rootTaskInfo.token : null; } @Override public void prepareMovingSplitScreenRoot(WindowContainerTransaction wct, int displayId) { // If multi split pair is supported, each display will have a root task, we won't need to // move split screen root to another display to make split pair work. if (enableMultiDisplaySplit()) { return; } // Find the current split-screen root task info. RunningTaskInfo currentRootTaskInfo = null; for (int id : mSplitMultiDisplayHelper.getCachedOrSystemDisplayIds()) { final RunningTaskInfo rootTaskInfo = mSplitMultiDisplayHelper.getDisplayRootTaskInfo(id); if (rootTaskInfo != null) { currentRootTaskInfo = rootTaskInfo; break; } } if (currentRootTaskInfo == null) { throw new IllegalStateException("Failed to find current split screen root task info."); } // If the task to be launched is on a different display than the current split-screen // root, reparent the entire split-screen root to the task's display. if (displayId != currentRootTaskInfo.displayId) { final DisplayAreaInfo targetDisplayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(displayId); if (targetDisplayAreaInfo != null) { wct.reparent(currentRootTaskInfo.token, targetDisplayAreaInfo.token, true /* onTop */); } } } class SplitRequest { @SplitPosition int mActivatePosition; Loading Loading @@ -867,7 +902,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // For now, the only CUJ that can use this is LaunchAdjacent while on non-default displays. if (enableNonDefaultDisplaySplit()) { prepareMovingSplitscreenRoot(wct, displayId); prepareMovingSplitScreenRoot(wct, displayId); } wct.sendPendingIntent(intent, fillInIntent, options); Loading Loading @@ -982,6 +1017,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } if (DesktopExperienceFlags.ENABLE_NON_DEFAULT_DISPLAY_SPLIT_BUGFIX.isTrue()) { // if the flag is enabled, split select can be initiated from external display. // Reparent the root task to correct display if necessary. final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(taskId); if (taskInfo != null) { prepareMovingSplitScreenRoot(wct, taskInfo.displayId); } } setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); StageTaskListener stageForTask1; Loading Loading @@ -2074,16 +2119,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setRootForceTranslucent(false, wct); } private void prepareMovingSplitscreenRoot(WindowContainerTransaction wct, int displayId) { if (!enableMultiDisplaySplit()) { final DisplayAreaInfo displayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(displayId); final WindowContainerToken token = getDisplayRootForDisplayId(DEFAULT_DISPLAY); if (token != null && displayAreaInfo != null) { wct.reparent(token, displayAreaInfo.token, true /* onTop */); } } } void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); mSplitLayout.updateStateWithCurrentPosition(); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +104 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; Loading Loading @@ -124,6 +125,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; Loading Loading @@ -671,6 +673,108 @@ public class StageCoordinatorTests extends ShellTestCase { assertThat(op.getDisallowOverrideBoundsForChildren()).isTrue(); } @Test @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenFlagEnabled_doesNothing() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; mStageCoordinator.prepareMovingSplitScreenRoot(mWct, DEFAULT_DISPLAY + 1); verify(mockHelper, never()).getCachedOrSystemDisplayIds(); verify(mRootTDAOrganizer, never()).getDisplayAreaInfo(anyInt()); verify(mWct, never()).reparent(any(), any(), anyBoolean()); } @Test(expected = IllegalStateException.class) @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenRootNotFound_throwsException() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; when(mockHelper.getCachedOrSystemDisplayIds()).thenReturn( new ArrayList<>(List.of(DEFAULT_DISPLAY))); when(mockHelper.getDisplayRootTaskInfo(anyInt())).thenReturn(null); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, DEFAULT_DISPLAY + 1); } @Test @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenTargetIsSameDisplay_doesNothing() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; final int targetDisplayId = DEFAULT_DISPLAY; ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(targetDisplayId) .build(); when(mockHelper.getCachedOrSystemDisplayIds()).thenReturn( new ArrayList<>(List.of(targetDisplayId))); when(mockHelper.getDisplayRootTaskInfo(targetDisplayId)) .thenReturn(currentRootTaskInfo); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId); verify(mWct, never()).reparent(any(), any(), anyBoolean()); } @Test @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenTargetIsDifferentDisplay_reparentsRoot() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; final int currentDisplayId = DEFAULT_DISPLAY; final int targetDisplayId = DEFAULT_DISPLAY + 1; WindowContainerToken currentRootToken = mock(WindowContainerToken.class); when(mRootDisplayAreaOrganizer.getDisplayTokenForDisplay(anyInt())) .thenReturn(mock(WindowContainerToken.class)); ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(currentDisplayId) .setToken(currentRootToken) .build(); when(mockHelper.getCachedOrSystemDisplayIds()) .thenReturn(new ArrayList<>(List.of(currentDisplayId, targetDisplayId))); when(mockHelper.getDisplayRootTaskInfo(currentDisplayId)) .thenReturn(currentRootTaskInfo); WindowContainerToken targetDisplayAreaToken = new MockToken().token(); DisplayAreaInfo targetDisplayAreaInfo = new DisplayAreaInfo(targetDisplayAreaToken, targetDisplayId, 0); when(mRootTDAOrganizer.getDisplayAreaInfo(targetDisplayId)) .thenReturn(targetDisplayAreaInfo); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId); verify(mWct).reparent(eq(currentRootToken), eq(targetDisplayAreaToken), eq(true)); } @Test @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenTargetDisplayAreaNotFound_doesNothing() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; final int currentDisplayId = DEFAULT_DISPLAY; final int targetDisplayId = DEFAULT_DISPLAY + 1; // Setup current root, but no target display area WindowContainerToken currentRootToken = mock(WindowContainerToken.class); ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(currentDisplayId) .setToken(currentRootToken) .build(); when(mockHelper.getCachedOrSystemDisplayIds()) .thenReturn(new ArrayList<>(List.of(currentDisplayId, targetDisplayId))); when(mockHelper.getDisplayRootTaskInfo(currentDisplayId)) .thenReturn(currentRootTaskInfo); when(mRootTDAOrganizer.getDisplayAreaInfo(targetDisplayId)).thenReturn(null); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId); verify(mWct, never()).reparent(any(), any(), anyBoolean()); } private Transitions createTestTransitions() { ShellInit shellInit = new ShellInit(mMainExecutor); final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.kt +12 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.wm.shell.splitscreen import android.window.WindowContainerToken import android.window.WindowContainerTransaction interface SplitMultiDisplayProvider { /** Loading @@ -26,4 +27,15 @@ interface SplitMultiDisplayProvider { * @return The {@link WindowContainerToken} associated with the display's root task. */ fun getDisplayRootForDisplayId(displayId: Int): WindowContainerToken? /** * Prepares to reparent the split-screen root to another display if the target task * resides on a different display. This is used to move the entire split-screen container * to the display where the user interaction is occurring. It only adds the reparent * operation to the given {@code wct} without executing it. * * @param wct The transaction to add the reparent operation to. * @param displayId The ID of the target display. */ fun prepareMovingSplitScreenRoot(wct: WindowContainerTransaction?, displayId: Int) }
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +46 −11 Original line number Diff line number Diff line Loading @@ -320,6 +320,41 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return rootTaskInfo != null ? rootTaskInfo.token : null; } @Override public void prepareMovingSplitScreenRoot(WindowContainerTransaction wct, int displayId) { // If multi split pair is supported, each display will have a root task, we won't need to // move split screen root to another display to make split pair work. if (enableMultiDisplaySplit()) { return; } // Find the current split-screen root task info. RunningTaskInfo currentRootTaskInfo = null; for (int id : mSplitMultiDisplayHelper.getCachedOrSystemDisplayIds()) { final RunningTaskInfo rootTaskInfo = mSplitMultiDisplayHelper.getDisplayRootTaskInfo(id); if (rootTaskInfo != null) { currentRootTaskInfo = rootTaskInfo; break; } } if (currentRootTaskInfo == null) { throw new IllegalStateException("Failed to find current split screen root task info."); } // If the task to be launched is on a different display than the current split-screen // root, reparent the entire split-screen root to the task's display. if (displayId != currentRootTaskInfo.displayId) { final DisplayAreaInfo targetDisplayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(displayId); if (targetDisplayAreaInfo != null) { wct.reparent(currentRootTaskInfo.token, targetDisplayAreaInfo.token, true /* onTop */); } } } class SplitRequest { @SplitPosition int mActivatePosition; Loading Loading @@ -867,7 +902,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // For now, the only CUJ that can use this is LaunchAdjacent while on non-default displays. if (enableNonDefaultDisplaySplit()) { prepareMovingSplitscreenRoot(wct, displayId); prepareMovingSplitScreenRoot(wct, displayId); } wct.sendPendingIntent(intent, fillInIntent, options); Loading Loading @@ -982,6 +1017,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } if (DesktopExperienceFlags.ENABLE_NON_DEFAULT_DISPLAY_SPLIT_BUGFIX.isTrue()) { // if the flag is enabled, split select can be initiated from external display. // Reparent the root task to correct display if necessary. final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(taskId); if (taskInfo != null) { prepareMovingSplitScreenRoot(wct, taskInfo.displayId); } } setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); StageTaskListener stageForTask1; Loading Loading @@ -2074,16 +2119,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setRootForceTranslucent(false, wct); } private void prepareMovingSplitscreenRoot(WindowContainerTransaction wct, int displayId) { if (!enableMultiDisplaySplit()) { final DisplayAreaInfo displayAreaInfo = mRootTDAOrganizer.getDisplayAreaInfo(displayId); final WindowContainerToken token = getDisplayRootForDisplayId(DEFAULT_DISPLAY); if (token != null && displayAreaInfo != null) { wct.reparent(token, displayAreaInfo.token, true /* onTop */); } } } void finishEnterSplitScreen(SurfaceControl.Transaction finishT) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "finishEnterSplitScreen"); mSplitLayout.updateStateWithCurrentPosition(); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +104 −0 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; Loading Loading @@ -124,6 +125,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; Loading Loading @@ -671,6 +673,108 @@ public class StageCoordinatorTests extends ShellTestCase { assertThat(op.getDisallowOverrideBoundsForChildren()).isTrue(); } @Test @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenFlagEnabled_doesNothing() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; mStageCoordinator.prepareMovingSplitScreenRoot(mWct, DEFAULT_DISPLAY + 1); verify(mockHelper, never()).getCachedOrSystemDisplayIds(); verify(mRootTDAOrganizer, never()).getDisplayAreaInfo(anyInt()); verify(mWct, never()).reparent(any(), any(), anyBoolean()); } @Test(expected = IllegalStateException.class) @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenRootNotFound_throwsException() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; when(mockHelper.getCachedOrSystemDisplayIds()).thenReturn( new ArrayList<>(List.of(DEFAULT_DISPLAY))); when(mockHelper.getDisplayRootTaskInfo(anyInt())).thenReturn(null); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, DEFAULT_DISPLAY + 1); } @Test @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenTargetIsSameDisplay_doesNothing() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; final int targetDisplayId = DEFAULT_DISPLAY; ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(targetDisplayId) .build(); when(mockHelper.getCachedOrSystemDisplayIds()).thenReturn( new ArrayList<>(List.of(targetDisplayId))); when(mockHelper.getDisplayRootTaskInfo(targetDisplayId)) .thenReturn(currentRootTaskInfo); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId); verify(mWct, never()).reparent(any(), any(), anyBoolean()); } @Test @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenTargetIsDifferentDisplay_reparentsRoot() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; final int currentDisplayId = DEFAULT_DISPLAY; final int targetDisplayId = DEFAULT_DISPLAY + 1; WindowContainerToken currentRootToken = mock(WindowContainerToken.class); when(mRootDisplayAreaOrganizer.getDisplayTokenForDisplay(anyInt())) .thenReturn(mock(WindowContainerToken.class)); ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(currentDisplayId) .setToken(currentRootToken) .build(); when(mockHelper.getCachedOrSystemDisplayIds()) .thenReturn(new ArrayList<>(List.of(currentDisplayId, targetDisplayId))); when(mockHelper.getDisplayRootTaskInfo(currentDisplayId)) .thenReturn(currentRootTaskInfo); WindowContainerToken targetDisplayAreaToken = new MockToken().token(); DisplayAreaInfo targetDisplayAreaInfo = new DisplayAreaInfo(targetDisplayAreaToken, targetDisplayId, 0); when(mRootTDAOrganizer.getDisplayAreaInfo(targetDisplayId)) .thenReturn(targetDisplayAreaInfo); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId); verify(mWct).reparent(eq(currentRootToken), eq(targetDisplayAreaToken), eq(true)); } @Test @DisableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MULTI_DISPLAY_SPLIT) public void moveSplitScreenRoot_whenTargetDisplayAreaNotFound_doesNothing() { SplitMultiDisplayHelper mockHelper = mock(SplitMultiDisplayHelper.class); mStageCoordinator.mSplitMultiDisplayHelper = mockHelper; final int currentDisplayId = DEFAULT_DISPLAY; final int targetDisplayId = DEFAULT_DISPLAY + 1; // Setup current root, but no target display area WindowContainerToken currentRootToken = mock(WindowContainerToken.class); ActivityManager.RunningTaskInfo currentRootTaskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(currentDisplayId) .setToken(currentRootToken) .build(); when(mockHelper.getCachedOrSystemDisplayIds()) .thenReturn(new ArrayList<>(List.of(currentDisplayId, targetDisplayId))); when(mockHelper.getDisplayRootTaskInfo(currentDisplayId)) .thenReturn(currentRootTaskInfo); when(mRootTDAOrganizer.getDisplayAreaInfo(targetDisplayId)).thenReturn(null); mStageCoordinator.prepareMovingSplitScreenRoot(mWct, targetDisplayId); verify(mWct, never()).reparent(any(), any(), anyBoolean()); } private Transitions createTestTransitions() { ShellInit shellInit = new ShellInit(mMainExecutor); final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class), Loading