Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +3 −0 Original line number Diff line number Diff line Loading @@ -1334,6 +1334,9 @@ class DesktopTasksController( val stageCoordinatorRootTaskToken = splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId(DEFAULT_DISPLAY) if (stageCoordinatorRootTaskToken == null) { return } wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */) val deactivationRunnable = Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayHelper.kt 0 → 100644 +113 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.splitscreen import android.app.ActivityManager import android.hardware.display.DisplayManager import android.view.SurfaceControl import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.split.SplitLayout import com.android.wm.shell.protolog.ShellProtoLogGroup /** * Helper class for managing split-screen functionality across multiple displays. */ class SplitMultiDisplayHelper(private val displayManager: DisplayManager) { /** * A map that stores the [SplitTaskHierarchy] associated with each display ID. * The keys are display IDs (integers), and the values are [SplitTaskHierarchy] objects, * which encapsulate the information needed to manage split-screen tasks on that display. */ private val displayTaskMap: MutableMap<Int, SplitTaskHierarchy> = mutableMapOf() /** * SplitTaskHierarchy is a class that encapsulates the components required * for managing split-screen functionality on a specific display. */ data class SplitTaskHierarchy( var rootTaskInfo: ActivityManager.RunningTaskInfo? = null, var mainStage: StageTaskListener? = null, var sideStage: StageTaskListener? = null, var rootTaskLeash: SurfaceControl? = null, var splitLayout: SplitLayout? = null ) /** * Returns a list of all currently connected display IDs. * * @return An ArrayList of display IDs. */ fun getDisplayIds(): ArrayList<Int> { val displayIds = ArrayList<Int>() displayManager.displays?.forEach { display -> displayIds.add(display.displayId) } return displayIds } /** * Swaps the [SplitTaskHierarchy] objects associated with two different display IDs. * * @param firstDisplayId The ID of the first display. * @param secondDisplayId The ID of the second display. */ fun swapDisplayTaskHierarchy(firstDisplayId: Int, secondDisplayId: Int) { if (!displayTaskMap.containsKey(firstDisplayId) || !displayTaskMap.containsKey(secondDisplayId)) { ProtoLog.w( ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Attempted to swap task hierarchies for invalid display IDs: %d, %d", firstDisplayId, secondDisplayId ) return } if (firstDisplayId == secondDisplayId) { return } val firstHierarchy = displayTaskMap[firstDisplayId] val secondHierarchy = displayTaskMap[secondDisplayId] displayTaskMap[firstDisplayId] = checkNotNull(secondHierarchy) displayTaskMap[secondDisplayId] = checkNotNull(firstHierarchy) } /** * Gets the root task info for the given display ID. * * @param displayId The ID of the display. * @return The root task info, or null if not found. */ fun getDisplayRootTaskInfo(displayId: Int): ActivityManager.RunningTaskInfo? { return displayTaskMap[displayId]?.rootTaskInfo } /** * Sets the root task info for the given display ID. * * @param displayId The ID of the display. * @param rootTaskInfo The root task info to set. */ fun setDisplayRootTaskInfo( displayId: Int, rootTaskInfo: ActivityManager.RunningTaskInfo? ) { val hierarchy = displayTaskMap.computeIfAbsent(displayId) { SplitTaskHierarchy() } hierarchy.rootTaskInfo = rootTaskInfo } } No newline at end of file libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.java→libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.kt +4 −4 Original line number Diff line number Diff line Loading @@ -14,16 +14,16 @@ * limitations under the License. */ package com.android.wm.shell.splitscreen; package com.android.wm.shell.splitscreen import android.window.WindowContainerToken; import android.window.WindowContainerToken public interface SplitMultiDisplayProvider { interface SplitMultiDisplayProvider { /** * Returns the WindowContainerToken for the root of the given display ID. * * @param displayId The ID of the display. * @return The {@link WindowContainerToken} associated with the display's root task. */ WindowContainerToken getDisplayRootForDisplayId(int displayId); fun getDisplayRootForDisplayId(displayId: Int): WindowContainerToken? } libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +8 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,7 @@ import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Debug; import android.os.Handler; Loading Loading @@ -278,6 +279,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // because we will be posting and removing it from the handler. private final Runnable mReEnableLaunchAdjacentOnRoot = () -> setLaunchAdjacentDisabled(false); private SplitMultiDisplayHelper mSplitMultiDisplayHelper; /** * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. Loading Loading @@ -393,6 +396,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDesktopTasksController = desktopTasksController; mRootTDAOrganizer = rootTDAOrganizer; DisplayManager displayManager = context.getSystemService(DisplayManager.class); mSplitMultiDisplayHelper = new SplitMultiDisplayHelper( Objects.requireNonNull(displayManager)); taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task"); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitMultiDisplayHelperTests.kt 0 → 100644 +168 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.splitscreen import android.app.ActivityManager import android.hardware.display.DisplayManager import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.split.SplitLayout import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations /** * Unit tests for [SplitMultiDisplayHelper]. */ @SmallTest @RunWith(AndroidJUnit4::class) class SplitMultiDisplayHelperTests : ShellTestCase() { private lateinit var splitMultiDisplayHelper: SplitMultiDisplayHelper @Mock private lateinit var mockDisplayManager: DisplayManager @Mock private lateinit var mockSplitLayout: SplitLayout @Mock private lateinit var mockDisplay1: Display @Mock private lateinit var mockDisplay2: Display @Before fun setUp() { MockitoAnnotations.initMocks(this) mockDisplay1 = mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY) ?: mock(Display::class.java) mockDisplay2 = mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY + 1) ?: mock(Display::class.java) `when`(mockDisplay1.displayId).thenReturn(Display.DEFAULT_DISPLAY) `when`(mockDisplay2.displayId).thenReturn(Display.DEFAULT_DISPLAY + 1) splitMultiDisplayHelper = SplitMultiDisplayHelper(mockDisplayManager) } @Test fun getDisplayIds_noDisplays_returnsEmptyList() { `when`(mockDisplayManager.displays).thenReturn(emptyArray()) val displayIds = splitMultiDisplayHelper.getDisplayIds() assertThat(displayIds).isEmpty() } @Test fun getDisplayIds_singleDisplay_returnsCorrectId() { `when`(mockDisplayManager.displays).thenReturn(arrayOf(mockDisplay1)) val displayIds = splitMultiDisplayHelper.getDisplayIds() assertThat(displayIds).containsExactly(Display.DEFAULT_DISPLAY) } @Test fun getDisplayIds_multiDisplays_returnsCorrectIds() { `when`(mockDisplayManager.displays).thenReturn(arrayOf(mockDisplay1, mockDisplay2)) val displayIds = splitMultiDisplayHelper.getDisplayIds() assertThat(displayIds).containsExactly(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY + 1) } @Test fun swapDisplayTaskHierarchy_validDisplays_swapsHierarchies() { val rootTaskInfo1 = ActivityManager.RunningTaskInfo().apply { taskId = 1 } val rootTaskInfo2 = ActivityManager.RunningTaskInfo().apply { taskId = 2 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo1) splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1, rootTaskInfo2) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY + 1) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY)).isEqualTo(rootTaskInfo2) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1)).isEqualTo(rootTaskInfo1) } @Test fun swapDisplayTaskHierarchy_invalidFirstDisplayId_doesNothing() { val rootTaskInfo2 = ActivityManager.RunningTaskInfo().apply { taskId = 2 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1, rootTaskInfo2) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.INVALID_DISPLAY, Display.DEFAULT_DISPLAY + 1) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.INVALID_DISPLAY)).isNull() assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1)).isEqualTo(rootTaskInfo2) } @Test fun swapDisplayTaskHierarchy_invalidSecondDisplayId_doesNothing() { val rootTaskInfo1 = ActivityManager.RunningTaskInfo().apply { taskId = 1 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo1) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.DEFAULT_DISPLAY, Display.INVALID_DISPLAY) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY)).isEqualTo(rootTaskInfo1) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.INVALID_DISPLAY)).isNull() } @Test fun swapDisplayTaskHierarchy_sameDisplayId_doesNothing() { val rootTaskInfo1 = ActivityManager.RunningTaskInfo().apply { taskId = 1 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo1) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY)).isEqualTo(rootTaskInfo1) } @Test fun getDisplayRootTaskInfo_validDisplayId_returnsRootTaskInfo() { val rootTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo) val retrievedRootTaskInfo = splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY) assertThat(retrievedRootTaskInfo).isEqualTo(rootTaskInfo) } @Test fun getDisplayRootTaskInfo_invalidDisplayId_returnsNull() { val retrievedRootTaskInfo = splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.INVALID_DISPLAY) assertThat(retrievedRootTaskInfo).isNull() } @Test fun setDisplayRootTaskInfo_setsRootTaskInfo() { val rootTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 456 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo) val retrievedRootTaskInfo = splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY) assertThat(retrievedRootTaskInfo).isEqualTo(rootTaskInfo) } } No newline at end of file Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +3 −0 Original line number Diff line number Diff line Loading @@ -1334,6 +1334,9 @@ class DesktopTasksController( val stageCoordinatorRootTaskToken = splitScreenController.multiDisplayProvider.getDisplayRootForDisplayId(DEFAULT_DISPLAY) if (stageCoordinatorRootTaskToken == null) { return } wct.reparent(stageCoordinatorRootTaskToken, displayAreaInfo.token, true /* onTop */) val deactivationRunnable = Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayHelper.kt 0 → 100644 +113 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.splitscreen import android.app.ActivityManager import android.hardware.display.DisplayManager import android.view.SurfaceControl import com.android.internal.protolog.ProtoLog import com.android.wm.shell.common.split.SplitLayout import com.android.wm.shell.protolog.ShellProtoLogGroup /** * Helper class for managing split-screen functionality across multiple displays. */ class SplitMultiDisplayHelper(private val displayManager: DisplayManager) { /** * A map that stores the [SplitTaskHierarchy] associated with each display ID. * The keys are display IDs (integers), and the values are [SplitTaskHierarchy] objects, * which encapsulate the information needed to manage split-screen tasks on that display. */ private val displayTaskMap: MutableMap<Int, SplitTaskHierarchy> = mutableMapOf() /** * SplitTaskHierarchy is a class that encapsulates the components required * for managing split-screen functionality on a specific display. */ data class SplitTaskHierarchy( var rootTaskInfo: ActivityManager.RunningTaskInfo? = null, var mainStage: StageTaskListener? = null, var sideStage: StageTaskListener? = null, var rootTaskLeash: SurfaceControl? = null, var splitLayout: SplitLayout? = null ) /** * Returns a list of all currently connected display IDs. * * @return An ArrayList of display IDs. */ fun getDisplayIds(): ArrayList<Int> { val displayIds = ArrayList<Int>() displayManager.displays?.forEach { display -> displayIds.add(display.displayId) } return displayIds } /** * Swaps the [SplitTaskHierarchy] objects associated with two different display IDs. * * @param firstDisplayId The ID of the first display. * @param secondDisplayId The ID of the second display. */ fun swapDisplayTaskHierarchy(firstDisplayId: Int, secondDisplayId: Int) { if (!displayTaskMap.containsKey(firstDisplayId) || !displayTaskMap.containsKey(secondDisplayId)) { ProtoLog.w( ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Attempted to swap task hierarchies for invalid display IDs: %d, %d", firstDisplayId, secondDisplayId ) return } if (firstDisplayId == secondDisplayId) { return } val firstHierarchy = displayTaskMap[firstDisplayId] val secondHierarchy = displayTaskMap[secondDisplayId] displayTaskMap[firstDisplayId] = checkNotNull(secondHierarchy) displayTaskMap[secondDisplayId] = checkNotNull(firstHierarchy) } /** * Gets the root task info for the given display ID. * * @param displayId The ID of the display. * @return The root task info, or null if not found. */ fun getDisplayRootTaskInfo(displayId: Int): ActivityManager.RunningTaskInfo? { return displayTaskMap[displayId]?.rootTaskInfo } /** * Sets the root task info for the given display ID. * * @param displayId The ID of the display. * @param rootTaskInfo The root task info to set. */ fun setDisplayRootTaskInfo( displayId: Int, rootTaskInfo: ActivityManager.RunningTaskInfo? ) { val hierarchy = displayTaskMap.computeIfAbsent(displayId) { SplitTaskHierarchy() } hierarchy.rootTaskInfo = rootTaskInfo } } No newline at end of file
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.java→libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitMultiDisplayProvider.kt +4 −4 Original line number Diff line number Diff line Loading @@ -14,16 +14,16 @@ * limitations under the License. */ package com.android.wm.shell.splitscreen; package com.android.wm.shell.splitscreen import android.window.WindowContainerToken; import android.window.WindowContainerToken public interface SplitMultiDisplayProvider { interface SplitMultiDisplayProvider { /** * Returns the WindowContainerToken for the root of the given display ID. * * @param displayId The ID of the display. * @return The {@link WindowContainerToken} associated with the display's root task. */ WindowContainerToken getDisplayRootForDisplayId(int displayId); fun getDisplayRootForDisplayId(displayId: Int): WindowContainerToken? }
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +8 −0 Original line number Diff line number Diff line Loading @@ -101,6 +101,7 @@ import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.os.Bundle; import android.os.Debug; import android.os.Handler; Loading Loading @@ -278,6 +279,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // because we will be posting and removing it from the handler. private final Runnable mReEnableLaunchAdjacentOnRoot = () -> setLaunchAdjacentDisabled(false); private SplitMultiDisplayHelper mSplitMultiDisplayHelper; /** * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. Loading Loading @@ -393,6 +396,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDesktopTasksController = desktopTasksController; mRootTDAOrganizer = rootTDAOrganizer; DisplayManager displayManager = context.getSystemService(DisplayManager.class); mSplitMultiDisplayHelper = new SplitMultiDisplayHelper( Objects.requireNonNull(displayManager)); taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Creating main/side root task"); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitMultiDisplayHelperTests.kt 0 → 100644 +168 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.wm.shell.splitscreen import android.app.ActivityManager import android.hardware.display.DisplayManager import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase import com.android.wm.shell.common.split.SplitLayout import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations /** * Unit tests for [SplitMultiDisplayHelper]. */ @SmallTest @RunWith(AndroidJUnit4::class) class SplitMultiDisplayHelperTests : ShellTestCase() { private lateinit var splitMultiDisplayHelper: SplitMultiDisplayHelper @Mock private lateinit var mockDisplayManager: DisplayManager @Mock private lateinit var mockSplitLayout: SplitLayout @Mock private lateinit var mockDisplay1: Display @Mock private lateinit var mockDisplay2: Display @Before fun setUp() { MockitoAnnotations.initMocks(this) mockDisplay1 = mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY) ?: mock(Display::class.java) mockDisplay2 = mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY + 1) ?: mock(Display::class.java) `when`(mockDisplay1.displayId).thenReturn(Display.DEFAULT_DISPLAY) `when`(mockDisplay2.displayId).thenReturn(Display.DEFAULT_DISPLAY + 1) splitMultiDisplayHelper = SplitMultiDisplayHelper(mockDisplayManager) } @Test fun getDisplayIds_noDisplays_returnsEmptyList() { `when`(mockDisplayManager.displays).thenReturn(emptyArray()) val displayIds = splitMultiDisplayHelper.getDisplayIds() assertThat(displayIds).isEmpty() } @Test fun getDisplayIds_singleDisplay_returnsCorrectId() { `when`(mockDisplayManager.displays).thenReturn(arrayOf(mockDisplay1)) val displayIds = splitMultiDisplayHelper.getDisplayIds() assertThat(displayIds).containsExactly(Display.DEFAULT_DISPLAY) } @Test fun getDisplayIds_multiDisplays_returnsCorrectIds() { `when`(mockDisplayManager.displays).thenReturn(arrayOf(mockDisplay1, mockDisplay2)) val displayIds = splitMultiDisplayHelper.getDisplayIds() assertThat(displayIds).containsExactly(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY + 1) } @Test fun swapDisplayTaskHierarchy_validDisplays_swapsHierarchies() { val rootTaskInfo1 = ActivityManager.RunningTaskInfo().apply { taskId = 1 } val rootTaskInfo2 = ActivityManager.RunningTaskInfo().apply { taskId = 2 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo1) splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1, rootTaskInfo2) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY + 1) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY)).isEqualTo(rootTaskInfo2) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1)).isEqualTo(rootTaskInfo1) } @Test fun swapDisplayTaskHierarchy_invalidFirstDisplayId_doesNothing() { val rootTaskInfo2 = ActivityManager.RunningTaskInfo().apply { taskId = 2 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1, rootTaskInfo2) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.INVALID_DISPLAY, Display.DEFAULT_DISPLAY + 1) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.INVALID_DISPLAY)).isNull() assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY + 1)).isEqualTo(rootTaskInfo2) } @Test fun swapDisplayTaskHierarchy_invalidSecondDisplayId_doesNothing() { val rootTaskInfo1 = ActivityManager.RunningTaskInfo().apply { taskId = 1 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo1) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.DEFAULT_DISPLAY, Display.INVALID_DISPLAY) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY)).isEqualTo(rootTaskInfo1) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.INVALID_DISPLAY)).isNull() } @Test fun swapDisplayTaskHierarchy_sameDisplayId_doesNothing() { val rootTaskInfo1 = ActivityManager.RunningTaskInfo().apply { taskId = 1 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo1) splitMultiDisplayHelper.swapDisplayTaskHierarchy(Display.DEFAULT_DISPLAY, Display.DEFAULT_DISPLAY) assertThat(splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY)).isEqualTo(rootTaskInfo1) } @Test fun getDisplayRootTaskInfo_validDisplayId_returnsRootTaskInfo() { val rootTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 123 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo) val retrievedRootTaskInfo = splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY) assertThat(retrievedRootTaskInfo).isEqualTo(rootTaskInfo) } @Test fun getDisplayRootTaskInfo_invalidDisplayId_returnsNull() { val retrievedRootTaskInfo = splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.INVALID_DISPLAY) assertThat(retrievedRootTaskInfo).isNull() } @Test fun setDisplayRootTaskInfo_setsRootTaskInfo() { val rootTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = 456 } splitMultiDisplayHelper.setDisplayRootTaskInfo(Display.DEFAULT_DISPLAY, rootTaskInfo) val retrievedRootTaskInfo = splitMultiDisplayHelper.getDisplayRootTaskInfo(Display.DEFAULT_DISPLAY) assertThat(retrievedRootTaskInfo).isEqualTo(rootTaskInfo) } } No newline at end of file