Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 16befe4e authored by Toshiki Kikuchi's avatar Toshiki Kikuchi
Browse files

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
parent 9d50b1b6
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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));
    }

+17 −3
Original line number Diff line number Diff line
@@ -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 }
+98 −69
Original line number Diff line number Diff line
@@ -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,19 +134,28 @@ 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 {
        setExtendedMode(true)

        connectExternalDisplay()
        if (expectToSwitch) {
            // Assumes [connectExternalDisplay] properly triggered the switching transition.
@@ -146,7 +180,6 @@ class DesktopDisplayModeControllerTest(
            verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
        }
    }
    }

    @Test
    @DisableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
@@ -176,16 +209,11 @@ class DesktopDisplayModeControllerTest(
            disconnectExternalDisplay()
        }
        setTabletModeStatus(tabletModeStatus)
        setExtendedMode(param.extendedDisplayEnabled)

        ExtendedDisplaySettingsSession(
                context.contentResolver,
                if (param.extendedDisplayEnabled) 1 else 0,
            )
            .use {
        assertThat(controller.getTargetWindowingModeForDefaultDisplay())
            .isEqualTo(param.expectedWindowingMode)
    }
    }

    @Test
    @EnableFlags(
@@ -199,35 +227,28 @@ 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)
    }
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
    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()

        val arg = argumentCaptor<WindowContainerTransaction>()
            verify(transitions, times(1))
                .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
        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
    @EnableFlags(Flags.FLAG_ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING)
@@ -236,19 +257,17 @@ class DesktopDisplayModeControllerTest(
        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
            WINDOWING_MODE_FULLSCREEN
        }
        setExtendedMode(true)

        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
        disconnectExternalDisplay()

        val arg = argumentCaptor<WindowContainerTransaction>()
            verify(transitions, times(1))
                .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
        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() {
        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
@@ -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,
            )
        }
    }