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

Commit 5b4a1c20 authored by Toshiki Kikuchi's avatar Toshiki Kikuchi
Browse files

Keep resolved windowing mode of existing tasks

This CL sets the windowing mode of the existing tasks explicitly to keep
their resolved windowing modes remain the same after the display
windowing mode switches back and forth.
Otherwise, the resolved mode may change if a task’s override windowing
mode is UNDEFINED (i.e., inherit its parent’s windowing mode).

Flag: com.android.window.flags.enable_display_windowing_mode_switching
Bug: 391255364
Test: DesktopDisplayEventHandlerTest
Change-Id: I4ae5be3874e9be603da1f6a38bc473f80dfe7364
parent 4d0fb74a
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -1210,7 +1210,8 @@ public abstract class WMShellModule {
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            IWindowManager windowManager,
            Optional<DesktopUserRepositories> desktopUserRepositories,
            Optional<DesktopTasksController> desktopTasksController
            Optional<DesktopTasksController> desktopTasksController,
            ShellTaskOrganizer shellTaskOrganizer
    ) {
        if (!DesktopModeStatus.canEnterDesktopMode(context)) {
            return Optional.empty();
@@ -1224,7 +1225,8 @@ public abstract class WMShellModule {
                        rootTaskDisplayAreaOrganizer,
                        windowManager,
                        desktopUserRepositories.get(),
                        desktopTasksController.get()));
                        desktopTasksController.get(),
                        shellTaskOrganizer));
    }

    @WMSingleton
+27 −1
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package com.android.wm.shell.desktopmode

import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.app.WindowConfiguration.windowingModeToString
import android.content.Context
import android.provider.Settings
import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
@@ -27,6 +30,7 @@ import android.window.WindowContainerTransaction
import com.android.internal.protolog.ProtoLog
import com.android.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
@@ -45,6 +49,7 @@ class DesktopDisplayEventHandler(
    private val windowManager: IWindowManager,
    private val desktopUserRepositories: DesktopUserRepositories,
    private val desktopTasksController: DesktopTasksController,
    private val shellTaskOrganizer: ShellTaskOrganizer,
) : OnDisplaysChangedListener, OnDeskRemovedListener {

    private val desktopRepository: DesktopRepository
@@ -119,13 +124,34 @@ class DesktopDisplayEventHandler(
            }
        val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
        requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
        if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) {
        val currentDisplayWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode
        if (currentDisplayWindowingMode == targetDisplayWindowingMode) {
            // Already in the target mode.
            return
        }

        logV(
            "As an external display is connected, changing default display's windowing mode from" +
                " ${windowingModeToString(currentDisplayWindowingMode)}" +
                " to ${windowingModeToString(targetDisplayWindowingMode)}"
        )

        val wct = WindowContainerTransaction()
        wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
        shellTaskOrganizer
            .getRunningTasks(DEFAULT_DISPLAY)
            .filter { it.activityType == ACTIVITY_TYPE_STANDARD }
            .forEach {
                // TODO: b/391965153 - Reconsider the logic under multi-desk window hierarchy
                when (it.windowingMode) {
                    currentDisplayWindowingMode -> {
                        wct.setWindowingMode(it.token, currentDisplayWindowingMode)
                    }
                    targetDisplayWindowingMode -> {
                        wct.setWindowingMode(it.token, WINDOWING_MODE_UNDEFINED)
                    }
                }
            }
        transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
    }

+76 −16
Original line number Diff line number Diff line
@@ -16,8 +16,10 @@

package com.android.wm.shell.desktopmode

import android.app.ActivityManager.RunningTaskInfo
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.os.Binder
import android.platform.test.annotations.EnableFlags
@@ -37,7 +39,9 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession
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.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
@@ -81,12 +85,20 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
    @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
    @Mock private lateinit var mockDesktopRepository: DesktopRepository
    @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
    @Mock private lateinit var shellTaskOrganizer: ShellTaskOrganizer

    private lateinit var mockitoSession: StaticMockitoSession
    private lateinit var shellInit: ShellInit
    private lateinit var handler: DesktopDisplayEventHandler

    private val onDisplaysChangedListenerCaptor = argumentCaptor<OnDisplaysChangedListener>()
    private val runningTasks = mutableListOf<RunningTaskInfo>()
    private val externalDisplayId = 100
    private val freeformTask =
        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FREEFORM).build()
    private val fullscreenTask =
        TestRunningTaskInfoBuilder().setWindowingMode(WINDOWING_MODE_FULLSCREEN).build()
    private val defaultTDA = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)

    @Before
    fun setUp() {
@@ -99,8 +111,8 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        shellInit = spy(ShellInit(testExecutor))
        whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
        whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
        val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
        whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
            .thenReturn(defaultTDA)
        handler =
            DesktopDisplayEventHandler(
                context,
@@ -111,7 +123,11 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
                mockWindowManager,
                mockDesktopUserRepositories,
                mockDesktopTasksController,
                shellTaskOrganizer,
            )
        whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
        runningTasks.add(freeformTask)
        runningTasks.add(fullscreenTask)
        shellInit.init()
        verify(displayController)
            .addDisplayWindowListener(onDisplaysChangedListenerCaptor.capture())
@@ -127,9 +143,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        extendedDisplayEnabled: Boolean,
        expectTransition: Boolean,
    ) {
        val externalDisplayId = 100
        val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
        tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
        defaultTDA.configuration.windowConfiguration.windowingMode = defaultWindowingMode
        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
        val settingsSession =
            ExtendedDisplaySettingsSession(
@@ -138,23 +152,17 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
            )

        settingsSession.use {
            // The external display connected.
            whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
                .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
            onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
            tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
            // The external display disconnected.
            whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
                .thenReturn(intArrayOf(DEFAULT_DISPLAY))
            onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
            connectExternalDisplay()
            defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
            disconnectExternalDisplay()

            if (expectTransition) {
                val arg = argumentCaptor<WindowContainerTransaction>()
                verify(transitions, times(2))
                    .startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
                assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
                assertThat(arg.firstValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
                    .isEqualTo(WINDOWING_MODE_FREEFORM)
                assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
                assertThat(arg.secondValue.changes[defaultTDA.token.asBinder()]?.windowingMode)
                    .isEqualTo(defaultWindowingMode)
            } else {
                verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
@@ -227,6 +235,58 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
    }

    @Test
    fun displayWindowingModeSwitch_existingTasksOnConnected() {
        defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FULLSCREEN
        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
            WINDOWING_MODE_FULLSCREEN
        }

        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
            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)
        }
    }

    @Test
    fun displayWindowingModeSwitch_existingTasksOnDisconnected() {
        defaultTDA.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
        whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer {
            WINDOWING_MODE_FULLSCREEN
        }

        ExtendedDisplaySettingsSession(context.contentResolver, 1).use {
            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)
        }
    }

    private fun connectExternalDisplay() {
        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
            .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
        onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)
    }

    private fun disconnectExternalDisplay() {
        whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
            .thenReturn(intArrayOf(DEFAULT_DISPLAY))
        onDisplaysChangedListenerCaptor.lastValue.onDisplayRemoved(externalDisplayId)
    }

    private class ExtendedDisplaySettingsSession(
        private val contentResolver: ContentResolver,
        private val overrideValue: Int,