• 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.persistence
18 
19 import android.content.Context
20 import android.view.Display
21 import android.window.DesktopExperienceFlags
22 import android.window.DesktopModeFlags
23 import com.android.internal.protolog.ProtoLog
24 import com.android.wm.shell.desktopmode.DesktopRepository
25 import com.android.wm.shell.desktopmode.DesktopUserRepositories
26 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer.DeskRecreationFactory
27 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
28 import com.android.wm.shell.shared.annotations.ShellMainThread
29 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
30 import kotlinx.coroutines.CoroutineScope
31 import kotlinx.coroutines.flow.MutableStateFlow
32 import kotlinx.coroutines.flow.StateFlow
33 import kotlinx.coroutines.launch
34 
35 /**
36  * Initializes the [DesktopRepository] from the [DesktopPersistentRepository].
37  *
38  * This class is responsible for reading the [DesktopPersistentRepository] and initializing the
39  * [DesktopRepository] with the tasks that previously existed in desktop.
40  */
41 class DesktopRepositoryInitializerImpl(
42     private val context: Context,
43     private val persistentRepository: DesktopPersistentRepository,
44     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
45 ) : DesktopRepositoryInitializer {
46 
47     override var deskRecreationFactory: DeskRecreationFactory = DefaultDeskRecreationFactory()
48 
49     private val _isInitialized = MutableStateFlow(false)
50     override val isInitialized: StateFlow<Boolean> = _isInitialized
51 
52     override fun initialize(userRepositories: DesktopUserRepositories) {
53         if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PERSISTENCE.isTrue) {
54             _isInitialized.value = true
55             return
56         }
57         //  TODO: b/365962554 - Handle the case that user moves to desktop before it's initialized
58         mainCoroutineScope.launch {
59             try {
60                 val desktopUserPersistentRepositoryMap =
61                     persistentRepository.getUserDesktopRepositoryMap() ?: return@launch
62                 for (userId in desktopUserPersistentRepositoryMap.keys) {
63                     val repository = userRepositories.getProfile(userId)
64                     val desktopRepositoryState =
65                         persistentRepository.getDesktopRepositoryState(userId) ?: continue
66                     val desksToRestore = getDesksToRestore(desktopRepositoryState, userId)
67                     logV(
68                         "initialize() will restore desks=%s user=%d",
69                         desksToRestore.map { it.desktopId },
70                         userId,
71                     )
72                     for (persistentDesktop in desksToRestore) {
73                         val maxTasks = getTaskLimit(persistentDesktop)
74                         val displayId = persistentDesktop.displayId
75                         val deskId = persistentDesktop.desktopId
76                         // TODO: b/401107440 - Implement desk restoration to other displays.
77                         val newDisplayId = Display.DEFAULT_DISPLAY
78                         val newDeskId =
79                             deskRecreationFactory.recreateDesk(
80                                 userId = userId,
81                                 destinationDisplayId = newDisplayId,
82                                 deskId = deskId,
83                             )
84                         if (newDeskId != null) {
85                             logV(
86                                 "Re-created desk=%d in display=%d using new" +
87                                     " deskId=%d and displayId=%d",
88                                 deskId,
89                                 displayId,
90                                 newDeskId,
91                                 newDisplayId,
92                             )
93                         }
94                         if (newDeskId == null || newDeskId != deskId || newDisplayId != displayId) {
95                             logV("Removing obsolete desk from persistence under deskId=%d", deskId)
96                             persistentRepository.removeDesktop(userId, deskId)
97                         }
98                         if (newDeskId == null) {
99                             logW(
100                                 "Could not re-create desk=%d from display=%d in displayId=%d",
101                                 deskId,
102                                 displayId,
103                                 newDisplayId,
104                             )
105                             continue
106                         }
107 
108                         // TODO: b/393961770 - [DesktopRepository] doesn't save desks to the
109                         //  persistent repository until a task is added to them. Update it so that
110                         //  empty desks can be restored too.
111                         repository.addDesk(displayId = displayId, deskId = newDeskId)
112                         var visibleTasksCount = 0
113                         persistentDesktop.zOrderedTasksList
114                             // Reverse it so we initialize the repo from bottom to top.
115                             .reversed()
116                             .mapNotNull { taskId -> persistentDesktop.tasksByTaskIdMap[taskId] }
117                             .forEach { task ->
118                                 // Visible here means non-minimized a.k.a. expanded, it does not
119                                 // mean
120                                 // it is visible in WM (and |DesktopRepository|) terms.
121                                 val isVisible =
122                                     task.desktopTaskState == DesktopTaskState.VISIBLE &&
123                                         visibleTasksCount < maxTasks
124 
125                                 repository.addTaskToDesk(
126                                     displayId = displayId,
127                                     deskId = newDeskId,
128                                     taskId = task.taskId,
129                                     isVisible = false,
130                                 )
131 
132                                 if (isVisible) {
133                                     visibleTasksCount++
134                                 } else {
135                                     repository.minimizeTaskInDesk(
136                                         displayId = displayId,
137                                         deskId = newDeskId,
138                                         taskId = task.taskId,
139                                     )
140                                 }
141 
142                                 if (task.desktopTaskTilingState == DesktopTaskTilingState.LEFT) {
143                                     repository.addLeftTiledTask(
144                                         persistentDesktop.displayId,
145                                         task.taskId,
146                                     )
147                                 } else if (
148                                     task.desktopTaskTilingState == DesktopTaskTilingState.RIGHT
149                                 ) {
150                                     repository.addRightTiledTask(
151                                         persistentDesktop.displayId,
152                                         task.taskId,
153                                     )
154                                 }
155                             }
156                     }
157                 }
158             } finally {
159                 _isInitialized.value = true
160             }
161         }
162     }
163 
164     private suspend fun getDesksToRestore(
165         state: DesktopRepositoryState,
166         userId: Int,
167     ): Set<Desktop> {
168         // TODO: b/365873835 - what about desks that won't be restored?
169         //  - invalid desk ids from multi-desk -> single-desk switching can be ignored / deleted.
170         val limitToSingleDeskPerDisplay =
171             !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
172         return state.desktopMap.keys
173             .mapNotNull { deskId ->
174                 persistentRepository.readDesktop(userId, deskId)?.takeIf { desk ->
175                     // Do not restore invalid desks when multi-desks is disabled. This is
176                     // possible if the feature is disabled after having created multiple desks.
177                     val isValidSingleDesk = desk.desktopId == desk.displayId
178                     (!limitToSingleDeskPerDisplay || isValidSingleDesk)
179                 }
180             }
181             .toSet()
182     }
183 
184     private fun getTaskLimit(persistedDesk: Desktop): Int =
185         DesktopModeStatus.getMaxTaskLimit(context).takeIf { it > 0 }
186             ?: persistedDesk.zOrderedTasksCount
187 
188     private fun logV(msg: String, vararg arguments: Any?) {
189         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
190     }
191 
192     private fun logW(msg: String, vararg arguments: Any?) {
193         ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
194     }
195 
196     /** A default implementation of [DeskRecreationFactory] that reuses the desk id. */
197     private class DefaultDeskRecreationFactory : DeskRecreationFactory {
198         override suspend fun recreateDesk(
199             userId: Int,
200             destinationDisplayId: Int,
201             deskId: Int,
202         ): Int = deskId
203     }
204 
205     companion object {
206         private const val TAG = "DesktopRepositoryInitializerImpl"
207     }
208 }
209