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