diff options
| author | 2025-03-03 15:06:00 +0900 | |
|---|---|---|
| committer | 2025-03-11 10:10:11 +0900 | |
| commit | 16befe4ec7fe6caade456a75460c9c4b1fde7ffa (patch) | |
| tree | f4a5a03023fc09978e9f18f0f516644fc7f374cc | |
| parent | 9d50b1b636caf7ae75f2c21055af0def15fc2ec5 (diff) | |
System decoration-based extended mode check
This CL changes the extended mode check to use the availability of
system decorations.
With ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, it is the source of truth
of the display “content mode” (i.e., extended or mirror).
Flag: com.android.server.display.feature.flags.enable_display_content_mode_management
Flag: com.android.window.flags.enable_display_windowing_mode_switching
Bug: 375319538
Test: DesktopDisplayModeControllerTest
Change-Id: I29885eff6252dd8ffdcd69511ad8017ae2388c03
3 files changed, 117 insertions, 72 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index bc2ed3f35b45..257e27a338be 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -1474,6 +1474,7 @@ public abstract class WMShellModule { ShellTaskOrganizer shellTaskOrganizer, DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider, InputManager inputManager, + DisplayController displayController, @ShellMainThread Handler mainHandler ) { if (!DesktopModeStatus.canEnterDesktopMode(context)) { @@ -1488,6 +1489,7 @@ public abstract class WMShellModule { shellTaskOrganizer, desktopWallpaperActivityTokenProvider, inputManager, + displayController, mainHandler)); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt index 904d86282c39..6dcc0deb1da1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayModeController.kt @@ -35,9 +35,11 @@ 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.common.DisplayController 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.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.transition.Transitions /** Controls the display windowing mode in desktop mode */ @@ -49,6 +51,7 @@ class DesktopDisplayModeController( private val shellTaskOrganizer: ShellTaskOrganizer, private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider, private val inputManager: InputManager, + private val displayController: DisplayController, @ShellMainThread private val mainHandler: Handler, ) { @@ -128,14 +131,25 @@ class DesktopDisplayModeController( return windowManager.getWindowingMode(DEFAULT_DISPLAY) } - // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available. - private fun isExtendedDisplayEnabled() = - 0 != + private fun isExtendedDisplayEnabled(): Boolean { + if (DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue) { + return rootTaskDisplayAreaOrganizer + .getDisplayIds() + .filter { it != DEFAULT_DISPLAY } + .any { displayId -> + displayController.getDisplay(displayId)?.let { display -> + DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display) + } ?: false + } + } + + return 0 != Settings.Global.getInt( context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0, ) + } private fun hasExternalDisplay() = rootTaskDisplayAreaOrganizer.getDisplayIds().any { it != DEFAULT_DISPLAY } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt index 450989dd334d..105941079095 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayModeControllerTest.kt @@ -26,28 +26,37 @@ import android.os.Binder import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.UsesFlags 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 import android.view.Display.DEFAULT_DISPLAY import android.view.IWindowManager import android.view.WindowManager.TRANSIT_CHANGE import android.window.DisplayAreaInfo import android.window.WindowContainerTransaction import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn +import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.never +import com.android.dx.mockito.inline.extended.StaticMockitoSession +import com.android.server.display.feature.flags.Flags as DisplayFlags import com.android.window.flags.Flags import com.android.wm.shell.MockToken import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.DisplayController import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus 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.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -60,6 +69,7 @@ import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness /** * Test class for [DesktopDisplayModeController] @@ -68,6 +78,7 @@ import org.mockito.kotlin.whenever */ @SmallTest @RunWith(TestParameterInjector::class) +@UsesFlags(com.android.server.display.feature.flags.Flags::class) class DesktopDisplayModeControllerTest( @TestParameter(valuesProvider = FlagsParameterizationProvider::class) flags: FlagsParameterization @@ -79,6 +90,7 @@ class DesktopDisplayModeControllerTest( private val desktopWallpaperActivityTokenProvider = mock<DesktopWallpaperActivityTokenProvider>() private val inputManager = mock<InputManager>() + private val displayController = mock<DisplayController>() private val mainHandler = mock<Handler>() private lateinit var controller: DesktopDisplayModeController @@ -90,6 +102,12 @@ class DesktopDisplayModeControllerTest( TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build() private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0) private val wallpaperToken = MockToken().token() + private val externalDisplay = mock<Display>() + + private lateinit var extendedDisplaySettingsRestoreSession: + ExtendedDisplaySettingsRestoreSession + + private lateinit var mockitoSession: StaticMockitoSession init { mSetFlagsRule.setFlagsParameterization(flags) @@ -97,6 +115,13 @@ class DesktopDisplayModeControllerTest( @Before fun setUp() { + mockitoSession = + mockitoSession() + .strictness(Strictness.LENIENT) + .spyStatic(DesktopModeStatus::class.java) + .startMocking() + extendedDisplaySettingsRestoreSession = + ExtendedDisplaySettingsRestoreSession(context.contentResolver) whenever(transitions.startTransition(anyInt(), any(), isNull())).thenReturn(Binder()) whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)) .thenReturn(defaultTDA) @@ -109,42 +134,50 @@ class DesktopDisplayModeControllerTest( shellTaskOrganizer, desktopWallpaperActivityTokenProvider, inputManager, + displayController, mainHandler, ) runningTasks.add(freeformTask) runningTasks.add(fullscreenTask) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(ArrayList(runningTasks)) whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(wallpaperToken) + whenever(displayController.getDisplay(EXTERNAL_DISPLAY_ID)).thenReturn(externalDisplay) setTabletModeStatus(SwitchState.UNKNOWN) } + @After + fun tearDown() { + extendedDisplaySettingsRestoreSession.restore() + mockitoSession.finishMocking() + } + 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. - // Will verify the transition later along with [disconnectExternalDisplay]. - defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM - } - disconnectExternalDisplay() + setExtendedMode(true) - if (expectToSwitch) { - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(2)) - .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - } else { - verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) - } + connectExternalDisplay() + if (expectToSwitch) { + // Assumes [connectExternalDisplay] properly triggered the switching transition. + // Will verify the transition later along with [disconnectExternalDisplay]. + defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM + } + disconnectExternalDisplay() + + if (expectToSwitch) { + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(2)) + .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[wallpaperToken.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + assertThat(arg.secondValue.changes[wallpaperToken.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) + } else { + verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull()) } } @@ -176,15 +209,10 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(tabletModeStatus) + setExtendedMode(param.extendedDisplayEnabled) - ExtendedDisplaySettingsSession( - context.contentResolver, - if (param.extendedDisplayEnabled) 1 else 0, - ) - .use { - assertThat(controller.getTargetWindowingModeForDefaultDisplay()) - .isEqualTo(param.expectedWindowingMode) - } + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) } @Test @@ -199,15 +227,10 @@ class DesktopDisplayModeControllerTest( disconnectExternalDisplay() } setTabletModeStatus(param.tabletModeStatus) + setExtendedMode(param.extendedDisplayEnabled) - ExtendedDisplaySettingsSession( - context.contentResolver, - if (param.extendedDisplayEnabled) 1 else 0, - ) - .use { - assertThat(controller.getTargetWindowingModeForDefaultDisplay()) - .isEqualTo(param.expectedWindowingMode) - } + assertThat(controller.getTargetWindowingModeForDefaultDisplay()) + .isEqualTo(param.expectedWindowingMode) } @Test @@ -215,18 +238,16 @@ class DesktopDisplayModeControllerTest( fun displayWindowingModeSwitch_existingTasksOnConnected() { defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN whenever(mockWindowManager.getWindowingMode(anyInt())).thenReturn(WINDOWING_MODE_FULLSCREEN) + setExtendedMode(true) - ExtendedDisplaySettingsSession(context.contentResolver, 1).use { - connectExternalDisplay() + connectExternalDisplay() - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FULLSCREEN) - } + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FULLSCREEN) } @Test @@ -236,18 +257,16 @@ class DesktopDisplayModeControllerTest( whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { WINDOWING_MODE_FULLSCREEN } + setExtendedMode(true) - ExtendedDisplaySettingsSession(context.contentResolver, 1).use { - disconnectExternalDisplay() + disconnectExternalDisplay() - val arg = argumentCaptor<WindowContainerTransaction>() - verify(transitions, times(1)) - .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) - assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_FREEFORM) - assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) - .isEqualTo(WINDOWING_MODE_UNDEFINED) - } + val arg = argumentCaptor<WindowContainerTransaction>() + verify(transitions, times(1)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull()) + assertThat(arg.firstValue.changes[freeformTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_FREEFORM) + assertThat(arg.firstValue.changes[fullscreenTask.token.asBinder()]?.windowingMode) + .isEqualTo(WINDOWING_MODE_UNDEFINED) } private fun connectExternalDisplay() { @@ -266,18 +285,27 @@ class DesktopDisplayModeControllerTest( whenever(inputManager.isInTabletMode()).thenReturn(status.value) } - private class ExtendedDisplaySettingsSession( - private val contentResolver: ContentResolver, - private val overrideValue: Int, - ) : AutoCloseable { + private fun setExtendedMode(enabled: Boolean) { + if (DisplayFlags.enableDisplayContentModeManagement()) { + doReturn(enabled).`when` { + DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, externalDisplay) + } + } else { + Settings.Global.putInt( + context.contentResolver, + DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, + if (enabled) 1 else 0, + ) + } + } + + private class ExtendedDisplaySettingsRestoreSession( + private val contentResolver: ContentResolver + ) { private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0) - init { - Settings.Global.putInt(contentResolver, settingName, overrideValue) - } - - override fun close() { + fun restore() { Settings.Global.putInt(contentResolver, settingName, initialValue) } } @@ -287,7 +315,8 @@ class DesktopDisplayModeControllerTest( context: TestParameterValuesProvider.Context ): List<FlagsParameterization> { return FlagsParameterization.allCombinationsOf( - Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH + Flags.FLAG_FORM_FACTOR_BASED_DESKTOP_FIRST_SWITCH, + DisplayFlags.FLAG_ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT, ) } } |