• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.desktopmode
18 
19 import android.content.Context
20 import android.view.Display
21 import android.view.Display.DEFAULT_DISPLAY
22 import android.window.DesktopExperienceFlags
23 import com.android.internal.protolog.ProtoLog
24 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
25 import com.android.wm.shell.common.DisplayController
26 import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
27 import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
28 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
29 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
30 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
31 import com.android.wm.shell.sysui.ShellController
32 import com.android.wm.shell.sysui.ShellInit
33 import com.android.wm.shell.sysui.UserChangeListener
34 import kotlinx.coroutines.CoroutineScope
35 import kotlinx.coroutines.cancel
36 import kotlinx.coroutines.launch
37 
38 /** Handles display events in desktop mode */
39 class DesktopDisplayEventHandler(
40     private val context: Context,
41     shellInit: ShellInit,
42     private val mainScope: CoroutineScope,
43     private val shellController: ShellController,
44     private val displayController: DisplayController,
45     private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
46     private val desktopRepositoryInitializer: DesktopRepositoryInitializer,
47     private val desktopUserRepositories: DesktopUserRepositories,
48     private val desktopTasksController: DesktopTasksController,
49     private val desktopDisplayModeController: DesktopDisplayModeController,
50 ) : OnDisplaysChangedListener, OnDeskRemovedListener {
51 
52     init {
53         shellInit.addInitCallback({ onInit() }, this)
54     }
55 
56     private fun onInit() {
57         displayController.addDisplayWindowListener(this)
58 
59         if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
60             desktopTasksController.onDeskRemovedListener = this
61 
62             shellController.addUserChangeListener(
63                 object : UserChangeListener {
64                     override fun onUserChanged(newUserId: Int, userContext: Context) {
65                         val displayIds = rootTaskDisplayAreaOrganizer.displayIds
66                         createDefaultDesksIfNeeded(displayIds.toSet(), newUserId)
67                     }
68                 }
69             )
70         }
71     }
72 
73     override fun onDisplayAdded(displayId: Int) {
74         if (displayId != DEFAULT_DISPLAY) {
75             desktopDisplayModeController.updateExternalDisplayWindowingMode(displayId)
76             // The default display's windowing mode depends on the availability of the external
77             // display. So updating the default display's windowing mode here.
78             desktopDisplayModeController.updateDefaultDisplayWindowingMode()
79         }
80 
81         createDefaultDesksIfNeeded(displayIds = setOf(displayId), userId = null)
82     }
83 
84     override fun onDisplayRemoved(displayId: Int) {
85         if (displayId != DEFAULT_DISPLAY) {
86             desktopDisplayModeController.updateDefaultDisplayWindowingMode()
87         }
88 
89         // TODO: b/362720497 - move desks in closing display to the remaining desk.
90     }
91 
92     override fun onDesktopModeEligibleChanged(displayId: Int) {
93         if (
94             DesktopExperienceFlags.ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT.isTrue &&
95                 displayId != DEFAULT_DISPLAY
96         ) {
97             desktopDisplayModeController.updateExternalDisplayWindowingMode(displayId)
98             // The default display's windowing mode depends on the desktop eligibility of the
99             // external display. So updating the default display's windowing mode here.
100             desktopDisplayModeController.updateDefaultDisplayWindowingMode()
101         }
102     }
103 
104     override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
105         createDefaultDesksIfNeeded(setOf(lastDisplayId), userId = null)
106     }
107 
108     private fun createDefaultDesksIfNeeded(displayIds: Set<Int>, userId: Int?) {
109         if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
110         logV("createDefaultDesksIfNeeded displays=%s", displayIds)
111         mainScope.launch {
112             desktopRepositoryInitializer.isInitialized.collect { initialized ->
113                 if (!initialized) return@collect
114                 val repository =
115                     userId?.let { desktopUserRepositories.getProfile(userId) }
116                         ?: desktopUserRepositories.current
117                 displayIds
118                     .filter { displayId -> displayId != Display.INVALID_DISPLAY }
119                     .filter { displayId -> supportsDesks(displayId) }
120                     .filter { displayId -> repository.getNumberOfDesks(displayId) == 0 }
121                     .also { displaysNeedingDesk ->
122                         logV(
123                             "createDefaultDesksIfNeeded creating default desks in displays=%s",
124                             displaysNeedingDesk,
125                         )
126                     }
127                     .forEach { displayId ->
128                         // TODO: b/393978539 - consider activating the desk on creation when
129                         //  applicable, such as for connected displays.
130                         desktopTasksController.createDesk(displayId, repository.userId)
131                     }
132                 cancel()
133             }
134         }
135     }
136 
137     // TODO: b/362720497 - connected/projected display considerations.
138     private fun supportsDesks(displayId: Int): Boolean =
139         DesktopModeStatus.canEnterDesktopMode(context)
140 
141     private fun logV(msg: String, vararg arguments: Any?) {
142         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
143     }
144 
145     companion object {
146         private const val TAG = "DesktopDisplayEventHandler"
147     }
148 }
149