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

Commit bec61ca2 authored by Jorge Gil's avatar Jorge Gil
Browse files

Desks: Create default desks on user changes

Creates a default empty desk in each eligible display when the user
changes.

Flag: com.android.window.flags.enable_multiple_desktops_backend
Bug: 402607975
Test: in an HSUM build, enter desktop - no crash
Change-Id: I3351e3c3fce29dfd23a9e3d412f93895043a6816
parent 2bb746d5
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -1341,7 +1341,9 @@ public abstract class WMShellModule {
            Context context,
            ShellInit shellInit,
            @ShellMainThread CoroutineScope mainScope,
            ShellController shellController,
            DisplayController displayController,
            RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            Optional<DesktopUserRepositories> desktopUserRepositories,
            Optional<DesktopTasksController> desktopTasksController,
            Optional<DesktopDisplayModeController> desktopDisplayModeController,
@@ -1355,7 +1357,9 @@ public abstract class WMShellModule {
                        context,
                        shellInit,
                        mainScope,
                        shellController,
                        displayController,
                        rootTaskDisplayAreaOrganizer,
                        desktopRepositoryInitializer,
                        desktopUserRepositories.get(),
                        desktopTasksController.get(),
+45 −20
Original line number Diff line number Diff line
@@ -17,16 +17,20 @@
package com.android.wm.shell.desktopmode

import android.content.Context
import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.window.DesktopExperienceFlags
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
@@ -36,7 +40,9 @@ class DesktopDisplayEventHandler(
    private val context: Context,
    shellInit: ShellInit,
    private val mainScope: CoroutineScope,
    private val shellController: ShellController,
    private val displayController: DisplayController,
    private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
    private val desktopRepositoryInitializer: DesktopRepositoryInitializer,
    private val desktopUserRepositories: DesktopUserRepositories,
    private val desktopTasksController: DesktopTasksController,
@@ -53,8 +59,17 @@ class DesktopDisplayEventHandler(
    private fun onInit() {
        displayController.addDisplayWindowListener(this)

        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
        if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
            desktopTasksController.onDeskRemovedListener = this

            shellController.addUserChangeListener(
                object : UserChangeListener {
                    override fun onUserChanged(newUserId: Int, userContext: Context) {
                        val displayIds = rootTaskDisplayAreaOrganizer.displayIds
                        createDefaultDesksIfNeeded(displayIds.toSet())
                    }
                }
            )
        }
    }

@@ -63,23 +78,7 @@ class DesktopDisplayEventHandler(
            desktopDisplayModeController.refreshDisplayWindowingMode()
        }

        if (!supportsDesks(displayId)) {
            logV("Display #$displayId does not support desks")
            return
        }

        mainScope.launch {
            desktopRepositoryInitializer.isInitialized.collect { initialized ->
                if (!initialized) return@collect
                if (desktopRepository.getNumberOfDesks(displayId) == 0) {
                    logV("Creating new desk in new display#$displayId")
                    // TODO: b/393978539 - consider activating the desk on creation when
                    //  applicable, such as for connected displays.
                    desktopTasksController.createDesk(displayId)
                }
                cancel()
            }
        }
        createDefaultDesksIfNeeded(displayIds = setOf(displayId))
    }

    override fun onDisplayRemoved(displayId: Int) {
@@ -93,8 +92,34 @@ class DesktopDisplayEventHandler(
    override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
        val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId)
        if (remainingDesks == 0) {
            logV("All desks removed from display#$lastDisplayId, creating empty desk")
            desktopTasksController.createDesk(lastDisplayId)
            logV("All desks removed from display#$lastDisplayId")
            createDefaultDesksIfNeeded(setOf(lastDisplayId))
        }
    }

    private fun createDefaultDesksIfNeeded(displayIds: Set<Int>) {
        if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
        logV("createDefaultDesksIfNeeded displays=%s", displayIds)
        mainScope.launch {
            desktopRepositoryInitializer.isInitialized.collect { initialized ->
                if (!initialized) return@collect
                displayIds
                    .filter { displayId -> displayId != Display.INVALID_DISPLAY }
                    .filter { displayId -> supportsDesks(displayId) }
                    .filter { displayId -> desktopRepository.getNumberOfDesks(displayId) == 0 }
                    .also { displaysNeedingDesk ->
                        logV(
                            "createDefaultDesksIfNeeded creating default desks in displays=%s",
                            displaysNeedingDesk,
                        )
                    }
                    .forEach { displayId ->
                        // TODO: b/393978539 - consider activating the desk on creation when
                        //  applicable, such as for connected displays.
                        desktopTasksController.createDesk(displayId)
                    }
                cancel()
            }
        }
    }

+65 −15
Original line number Diff line number Diff line
@@ -24,13 +24,16 @@ 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.window.flags.Flags
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.UserChangeListener
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,6 +49,7 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness

@@ -60,6 +64,8 @@ import org.mockito.quality.Strictness
class DesktopDisplayEventHandlerTest : ShellTestCase() {
    @Mock lateinit var testExecutor: ShellExecutor
    @Mock lateinit var displayController: DisplayController
    @Mock private lateinit var mockShellController: ShellController
    @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
    @Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
    @Mock private lateinit var mockDesktopRepository: DesktopRepository
    @Mock private lateinit var mockDesktopTasksController: DesktopTasksController
@@ -89,7 +95,9 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
                context,
                shellInit,
                testScope.backgroundScope,
                mockShellController,
                displayController,
                mockRootTaskDisplayAreaOrganizer,
                desktopRepositoryInitializer,
                mockDesktopUserRepositories,
                mockDesktopTasksController,
@@ -107,6 +115,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_createsDesk() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -119,6 +128,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -130,6 +140,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDisplayAdded_supportsDesks_desktopRepositoryInitializedTwice_createsDeskOnce() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -143,6 +154,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -156,34 +168,72 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
        }

    @Test
    fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() {
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
            desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)

            onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
            runCurrent()

            verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDeskRemoved_noDesksRemain_createsDesk() {
    fun testDeskRemoved_noDesksRemain_createsDesk() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
            whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(0)
            desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)

            handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
            runCurrent()

            verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testDeskRemoved_desksRemain_doesNotCreateDesk() {
    fun testDeskRemoved_desksRemain_doesNotCreateDesk() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
            whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
            desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)

            handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
            runCurrent()

            verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
        }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
    fun testUserChanged_createsDeskWhenNeeded() =
        testScope.runTest {
            whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
            val userChangeListenerCaptor = argumentCaptor<UserChangeListener>()
            verify(mockShellController).addUserChangeListener(userChangeListenerCaptor.capture())
            whenever(mockDesktopRepository.getNumberOfDesks(displayId = 2)).thenReturn(0)
            whenever(mockDesktopRepository.getNumberOfDesks(displayId = 3)).thenReturn(0)
            whenever(mockDesktopRepository.getNumberOfDesks(displayId = 4)).thenReturn(1)
            whenever(mockRootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(2, 3, 4))
            desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
            handler.onDisplayAdded(displayId = 2)
            handler.onDisplayAdded(displayId = 3)
            handler.onDisplayAdded(displayId = 4)
            runCurrent()

            clearInvocations(mockDesktopTasksController)
            userChangeListenerCaptor.lastValue.onUserChanged(1, context)
            runCurrent()

            verify(mockDesktopTasksController).createDesk(displayId = 2)
            verify(mockDesktopTasksController).createDesk(displayId = 3)
            verify(mockDesktopTasksController, never()).createDesk(displayId = 4)
        }

    @Test
    fun testConnectExternalDisplay() {
        onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(externalDisplayId)