Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -1472,7 +1472,9 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, IWindowManager windowManager, ShellTaskOrganizer shellTaskOrganizer, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, InputManager inputManager, @ShellMainThread Handler mainHandler ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); Loading @@ -1484,7 +1486,9 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, windowManager, shellTaskOrganizer, desktopWallpaperActivityTokenProvider)); desktopWallpaperActivityTokenProvider, inputManager, mainHandler)); } // Loading libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +35 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.windowingModeToString import android.content.Context import android.hardware.input.InputManager import android.os.Handler import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY Loading @@ -29,11 +31,13 @@ import android.view.IWindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.DesktopExperienceFlags import android.window.WindowContainerTransaction import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions /** Controls the display windowing mode in desktop mode */ Loading @@ -44,8 +48,26 @@ class DesktopDisplayModeController( private val windowManager: IWindowManager, private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, private val inputManager: InputManager, @ShellMainThread private val mainHandler: Handler, ) { private val onTabletModeChangedListener = object : InputManager.OnTabletModeChangedListener { override fun onTabletModeChanged(whenNanos: Long, inTabletMode: Boolean) { refreshDisplayWindowingMode() } } init { if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { inputManager.registerOnTabletModeChangedListener( onTabletModeChangedListener, mainHandler, ) } } fun refreshDisplayWindowingMode() { if (!DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) return Loading Loading @@ -89,10 +111,20 @@ class DesktopDisplayModeController( transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } private fun getTargetWindowingModeForDefaultDisplay(): Int { @VisibleForTesting fun getTargetWindowingModeForDefaultDisplay(): Int { if (isExtendedDisplayEnabled() && hasExternalDisplay()) { return WINDOWING_MODE_FREEFORM } if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { if (isInClamshellMode()) { return WINDOWING_MODE_FREEFORM } return WINDOWING_MODE_FULLSCREEN } // If form factor-based desktop first switch is disabled, use the default display windowing // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC). return windowManager.getWindowingMode(DEFAULT_DISPLAY) } Loading @@ -108,6 +140,8 @@ class DesktopDisplayModeController( private fun hasExternalDisplay() = rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY } private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +216 −42 Original line number Diff line number Diff line Loading @@ -21,9 +21,12 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ContentResolver import android.hardware.input.InputManager import android.os.Binder import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY Loading @@ -44,6 +47,7 @@ import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider import org.junit.Before import org.junit.Test import org.junit.runner.RunWith Loading @@ -64,13 +68,18 @@ import org.mockito.kotlin.whenever */ @SmallTest @RunWith(TestParameterInjector::class) class DesktopDisplayModeControllerTest : ShellTestCase() { class DesktopDisplayModeControllerTest( @TestParameter(valuesProvider = FlagsParameterizationProvider::class) flags: FlagsParameterization ) : ShellTestCase() { private val transitions = mock<Transitions>() private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() private val mockWindowManager = mock<IWindowManager>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() private val inputManager = mock<InputManager>() private val mainHandler = mock<Handler>() private lateinit var controller: DesktopDisplayModeController Loading @@ -82,6 +91,10 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() init { mSetFlagsRule.setFlagsParameterization(flags) } @Before fun setUp() { whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder()) Loading @@ -95,27 +108,20 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { mockWindowManager, shellTaskOrganizer, desktopWallpaperActivityTokenProvider, inputManager, mainHandler, ) runningTasks.add(freeformTask) runningTasks.add(fullscreenTask) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) setTabletModeStatus(SwitchState.UNKNOWN) } private fun testDisplayWindowingModeSwitch( defaultWindowingMode: Int, extendedDisplayEnabled: Boolean, expectToSwitch: Boolean, ) { defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode) val settingsSession = ExtendedDisplaySettingsSession( context.contentResolver, if (extendedDisplayEnabled) 1 else 0, ) settingsSession.use { private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) { defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN) ExtendedDisplaySettingsSession(context.contentResolver, 1).use { connectExternalDisplay() if (expectToSwitch) { // Assumes [connectExternalDisplay] properly triggered the switching transition. Loading @@ -133,7 +139,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) .isEqualTo(defaultWindowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) } else { Loading @@ -144,25 +150,64 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled( @TestParameter param: ModeSwitchTestCase ) { testDisplayWindowingModeSwitch( param.defaultWindowingMode, param.extendedDisplayEnabled, fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled() { // When the flag is disabled, never switch. expectToSwitch = false, ) testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ false) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) fun displayWindowingModeSwitchOnDisplayConnected() { testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ true) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) fun displayWindowingModeSwitchOnDisplayConnected(@TestParameter param: ModeSwitchTestCase) { testDisplayWindowingModeSwitch( param.defaultWindowingMode, param.extendedDisplayEnabled, param.expectToSwitchByDefault, @DisableFlags(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH) fun testTargetWindowingMode_formfactorDisabled( @TestParameter param: ExternalDisplayBasedTargetModeTestCase, @TestParameter tabletModeStatus: SwitchState, ) { whenever(mockWindowManager.getWindowingMode(anyInt())) .thenReturn(param.defaultWindowingMode) if (param.hasExternalDisplay) { connectExternalDisplay() } else { disconnectExternalDisplay() } setTabletModeStatus(tabletModeStatus) ExtendedDisplaySettingsSession( context.contentResolver, if (param.extendedDisplayEnabled) 1 else 0, ) .use { assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) } } @Test @EnableFlags( Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, ) fun testTargetWindowingMode(@TestParameter param: FormFactorBasedTargetModeTestCase) { if (param.hasExternalDisplay) { connectExternalDisplay() } else { disconnectExternalDisplay() } setTabletModeStatus(param.tabletModeStatus) ExtendedDisplaySettingsSession( context.contentResolver, if (param.extendedDisplayEnabled) 1 else 0, ) .use { assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) } } @Test Loading Loading @@ -217,6 +262,10 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { controller.refreshDisplayWindowingMode() } private fun setTabletModeStatus(status: SwitchState) { whenever(inputManager.isInTabletMode()).thenReturn(status.value) } private class ExtendedDisplaySettingsSession( private val contentResolver: ContentResolver, private val overrideValue: Int, Loading @@ -233,33 +282,158 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { } } private class FlagsParameterizationProvider : TestParameterValuesProvider() { override fun provideValues( context: TestParameterValuesProvider.Context ): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf( Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH ) } } companion object { const val EXTERNAL_DISPLAY_ID = 100 enum class ModeSwitchTestCase( enum class SwitchState(val value: Int) { UNKNOWN(InputManager.SWITCH_STATE_UNKNOWN), ON(InputManager.SWITCH_STATE_ON), OFF(InputManager.SWITCH_STATE_OFF), } enum class ExternalDisplayBasedTargetModeTestCase( val defaultWindowingMode: Int, val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, val expectToSwitchByDefault: Boolean, val expectedWindowingMode: Int, ) { FULLSCREEN_DISPLAY( FREEFORM_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = true, expectToSwitchByDefault = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_DISPLAY_MIRRORING( FREEFORM_NO_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_NO_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), FREEFORM_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = false, expectToSwitchByDefault = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FREEFORM_DISPLAY( FULLSCREEN_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), FREEFORM_NO_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_NO_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } enum class FormFactorBasedTargetModeTestCase( val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, val tabletModeStatus: SwitchState, val expectedWindowingMode: Int, ) { EXTERNAL_EXTENDED_TABLET( hasExternalDisplay = true, extendedDisplayEnabled = true, expectToSwitchByDefault = false, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FREEFORM_DISPLAY_MIRRORING( defaultWindowingMode = WINDOWING_MODE_FREEFORM, NO_EXTERNAL_EXTENDED_TABLET( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), EXTERNAL_MIRROR_TABLET( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), NO_EXTERNAL_MIRROR_TABLET( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), EXTERNAL_EXTENDED_CLAMSHELL( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), NO_EXTERNAL_EXTENDED_CLAMSHELL( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), EXTERNAL_MIRROR_CLAMSHELL( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), NO_EXTERNAL_MIRROR_CLAMSHELL( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), EXTERNAL_EXTENDED_UNKNOWN( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), NO_EXTERNAL_EXTENDED_UNKNOWN( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), EXTERNAL_MIRROR_UNKNOWN( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), NO_EXTERNAL_MIRROR_UNKNOWN( hasExternalDisplay = false, extendedDisplayEnabled = false, expectToSwitchByDefault = false, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } } Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +6 −2 Original line number Diff line number Diff line Loading @@ -1472,7 +1472,9 @@ public abstract class WMShellModule { RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, IWindowManager windowManager, ShellTaskOrganizer shellTaskOrganizer, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, InputManager inputManager, @ShellMainThread Handler mainHandler ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { return Optional.empty(); Loading @@ -1484,7 +1486,9 @@ public abstract class WMShellModule { rootTaskDisplayAreaOrganizer, windowManager, shellTaskOrganizer, desktopWallpaperActivityTokenProvider)); desktopWallpaperActivityTokenProvider, inputManager, mainHandler)); } // Loading
libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +35 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.windowingModeToString import android.content.Context import android.hardware.input.InputManager import android.os.Handler import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY Loading @@ -29,11 +31,13 @@ import android.view.IWindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.DesktopExperienceFlags import android.window.WindowContainerTransaction import com.android.internal.annotations.VisibleForTesting import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions /** Controls the display windowing mode in desktop mode */ Loading @@ -44,8 +48,26 @@ class DesktopDisplayModeController( private val windowManager: IWindowManager, private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, private val inputManager: InputManager, @ShellMainThread private val mainHandler: Handler, ) { private val onTabletModeChangedListener = object : InputManager.OnTabletModeChangedListener { override fun onTabletModeChanged(whenNanos: Long, inTabletMode: Boolean) { refreshDisplayWindowingMode() } } init { if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { inputManager.registerOnTabletModeChangedListener( onTabletModeChangedListener, mainHandler, ) } } fun refreshDisplayWindowingMode() { if (!DesktopExperienceFlags.ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING.isTrue) return Loading Loading @@ -89,10 +111,20 @@ class DesktopDisplayModeController( transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null) } private fun getTargetWindowingModeForDefaultDisplay(): Int { @VisibleForTesting fun getTargetWindowingModeForDefaultDisplay(): Int { if (isExtendedDisplayEnabled() && hasExternalDisplay()) { return WINDOWING_MODE_FREEFORM } if (DesktopExperienceFlags.FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH.isTrue) { if (isInClamshellMode()) { return WINDOWING_MODE_FREEFORM } return WINDOWING_MODE_FULLSCREEN } // If form factor-based desktop first switch is disabled, use the default display windowing // mode here to keep the freeform mode for some form factors (e.g., FEATURE_PC). return windowManager.getWindowingMode(DEFAULT_DISPLAY) } Loading @@ -108,6 +140,8 @@ class DesktopDisplayModeController( private fun hasExternalDisplay() = rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY } private fun isInClamshellMode() = inputManager.isInTabletMode() == InputManager.SWITCH_STATE_OFF private fun logV(msg: String, vararg arguments: Any?) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments) } Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +216 −42 Original line number Diff line number Diff line Loading @@ -21,9 +21,12 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.content.ContentResolver import android.hardware.input.InputManager import android.os.Binder import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import android.provider.Settings import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS import android.view.Display.DEFAULT_DISPLAY Loading @@ -44,6 +47,7 @@ import com.android.wm.shell.transition.Transitions import com.google.common.truth.Truth.assertThat import com.google.testing.junit.testparameterinjector.TestParameter import com.google.testing.junit.testparameterinjector.TestParameterInjector import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider import org.junit.Before import org.junit.Test import org.junit.runner.RunWith Loading @@ -64,13 +68,18 @@ import org.mockito.kotlin.whenever */ @SmallTest @RunWith(TestParameterInjector::class) class DesktopDisplayModeControllerTest : ShellTestCase() { class DesktopDisplayModeControllerTest( @TestParameter(valuesProvider = FlagsParameterizationProvider::class) flags: FlagsParameterization ) : ShellTestCase() { private val transitions = mock<Transitions>() private val rootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>() private val mockWindowManager = mock<IWindowManager>() private val shellTaskOrganizer = mock<ShellTaskOrganizer>() private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() private val inputManager = mock<InputManager>() private val mainHandler = mock<Handler>() private lateinit var controller: DesktopDisplayModeController Loading @@ -82,6 +91,10 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() init { mSetFlagsRule.setFlagsParameterization(flags) } @Before fun setUp() { whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder()) Loading @@ -95,27 +108,20 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { mockWindowManager, shellTaskOrganizer, desktopWallpaperActivityTokenProvider, inputManager, mainHandler, ) runningTasks.add(freeformTask) runningTasks.add(fullscreenTask) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) setTabletModeStatus(SwitchState.UNKNOWN) } private fun testDisplayWindowingModeSwitch( defaultWindowingMode: Int, extendedDisplayEnabled: Boolean, expectToSwitch: Boolean, ) { defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(defaultWindowingMode) val settingsSession = ExtendedDisplaySettingsSession( context.contentResolver, if (extendedDisplayEnabled) 1 else 0, ) settingsSession.use { private fun testDisplayWindowingModeSwitchOnDisplayConnected(expectToSwitch: Boolean) { defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN) ExtendedDisplaySettingsSession(context.contentResolver, 1).use { connectExternalDisplay() if (expectToSwitch) { // Assumes [connectExternalDisplay] properly triggered the switching transition. Loading @@ -133,7 +139,7 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) .isEqualTo(defaultWindowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) .isEqualTo(WINDOWING_MODE_FULLSCREEN) } else { Loading @@ -144,25 +150,64 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { @Test @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled( @TestParameter param: ModeSwitchTestCase ) { testDisplayWindowingModeSwitch( param.defaultWindowingMode, param.extendedDisplayEnabled, fun displayWindowingModeSwitchOnDisplayConnected_flagDisabled() { // When the flag is disabled, never switch. expectToSwitch = false, ) testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ false) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) fun displayWindowingModeSwitchOnDisplayConnected() { testDisplayWindowingModeSwitchOnDisplayConnected(/* expectToSwitch= */ true) } @Test @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING) fun displayWindowingModeSwitchOnDisplayConnected(@TestParameter param: ModeSwitchTestCase) { testDisplayWindowingModeSwitch( param.defaultWindowingMode, param.extendedDisplayEnabled, param.expectToSwitchByDefault, @DisableFlags(Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH) fun testTargetWindowingMode_formfactorDisabled( @TestParameter param: ExternalDisplayBasedTargetModeTestCase, @TestParameter tabletModeStatus: SwitchState, ) { whenever(mockWindowManager.getWindowingMode(anyInt())) .thenReturn(param.defaultWindowingMode) if (param.hasExternalDisplay) { connectExternalDisplay() } else { disconnectExternalDisplay() } setTabletModeStatus(tabletModeStatus) ExtendedDisplaySettingsSession( context.contentResolver, if (param.extendedDisplayEnabled) 1 else 0, ) .use { assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) } } @Test @EnableFlags( Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING, Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, ) fun testTargetWindowingMode(@TestParameter param: FormFactorBasedTargetModeTestCase) { if (param.hasExternalDisplay) { connectExternalDisplay() } else { disconnectExternalDisplay() } setTabletModeStatus(param.tabletModeStatus) ExtendedDisplaySettingsSession( context.contentResolver, if (param.extendedDisplayEnabled) 1 else 0, ) .use { assertThat(controller.getTargetWindowingModeForDefaultDisplay()) .isEqualTo(param.expectedWindowingMode) } } @Test Loading Loading @@ -217,6 +262,10 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { controller.refreshDisplayWindowingMode() } private fun setTabletModeStatus(status: SwitchState) { whenever(inputManager.isInTabletMode()).thenReturn(status.value) } private class ExtendedDisplaySettingsSession( private val contentResolver: ContentResolver, private val overrideValue: Int, Loading @@ -233,33 +282,158 @@ class DesktopDisplayModeControllerTest : ShellTestCase() { } } private class FlagsParameterizationProvider : TestParameterValuesProvider() { override fun provideValues( context: TestParameterValuesProvider.Context ): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf( Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH ) } } companion object { const val EXTERNAL_DISPLAY_ID = 100 enum class ModeSwitchTestCase( enum class SwitchState(val value: Int) { UNKNOWN(InputManager.SWITCH_STATE_UNKNOWN), ON(InputManager.SWITCH_STATE_ON), OFF(InputManager.SWITCH_STATE_OFF), } enum class ExternalDisplayBasedTargetModeTestCase( val defaultWindowingMode: Int, val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, val expectToSwitchByDefault: Boolean, val expectedWindowingMode: Int, ) { FULLSCREEN_DISPLAY( FREEFORM_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = true, expectToSwitchByDefault = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_DISPLAY_MIRRORING( FREEFORM_NO_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = true, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_NO_EXTERNAL_EXTENDED( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = true, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), FREEFORM_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = true, extendedDisplayEnabled = false, expectToSwitchByDefault = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FREEFORM_DISPLAY( FULLSCREEN_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = true, extendedDisplayEnabled = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), FREEFORM_NO_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FREEFORM, hasExternalDisplay = false, extendedDisplayEnabled = false, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FULLSCREEN_NO_EXTERNAL_MIRROR( defaultWindowingMode = WINDOWING_MODE_FULLSCREEN, hasExternalDisplay = false, extendedDisplayEnabled = false, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } enum class FormFactorBasedTargetModeTestCase( val hasExternalDisplay: Boolean, val extendedDisplayEnabled: Boolean, val tabletModeStatus: SwitchState, val expectedWindowingMode: Int, ) { EXTERNAL_EXTENDED_TABLET( hasExternalDisplay = true, extendedDisplayEnabled = true, expectToSwitchByDefault = false, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), FREEFORM_DISPLAY_MIRRORING( defaultWindowingMode = WINDOWING_MODE_FREEFORM, NO_EXTERNAL_EXTENDED_TABLET( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), EXTERNAL_MIRROR_TABLET( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), NO_EXTERNAL_MIRROR_TABLET( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.ON, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), EXTERNAL_EXTENDED_CLAMSHELL( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), NO_EXTERNAL_EXTENDED_CLAMSHELL( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), EXTERNAL_MIRROR_CLAMSHELL( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), NO_EXTERNAL_MIRROR_CLAMSHELL( hasExternalDisplay = false, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.OFF, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), EXTERNAL_EXTENDED_UNKNOWN( hasExternalDisplay = true, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FREEFORM, ), NO_EXTERNAL_EXTENDED_UNKNOWN( hasExternalDisplay = false, extendedDisplayEnabled = true, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), EXTERNAL_MIRROR_UNKNOWN( hasExternalDisplay = true, extendedDisplayEnabled = false, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), NO_EXTERNAL_MIRROR_UNKNOWN( hasExternalDisplay = false, extendedDisplayEnabled = false, expectToSwitchByDefault = false, tabletModeStatus = SwitchState.UNKNOWN, expectedWindowingMode = WINDOWING_MODE_FULLSCREEN, ), } } Loading