• 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.app.ActivityManager
20 import android.content.Context
21 import android.content.pm.UserInfo
22 import android.os.UserManager
23 import android.util.SparseArray
24 import android.window.DesktopExperienceFlags
25 import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM
26 import androidx.core.util.forEach
27 import com.android.internal.protolog.ProtoLog
28 import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
29 import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
30 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
31 import com.android.wm.shell.shared.annotations.ShellMainThread
32 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
33 import com.android.wm.shell.sysui.ShellController
34 import com.android.wm.shell.sysui.ShellInit
35 import com.android.wm.shell.sysui.UserChangeListener
36 import java.io.PrintWriter
37 import kotlinx.coroutines.CoroutineScope
38 import kotlinx.coroutines.launch
39 
40 /** Manages per-user DesktopRepository instances. */
41 class DesktopUserRepositories(
42     context: Context,
43     shellInit: ShellInit,
44     private val shellController: ShellController,
45     private val persistentRepository: DesktopPersistentRepository,
46     private val repositoryInitializer: DesktopRepositoryInitializer,
47     @ShellMainThread private val mainCoroutineScope: CoroutineScope,
48     private val userManager: UserManager,
49 ) : UserChangeListener {
50     private var userId: Int
51     private var userIdToProfileIdsMap: MutableMap<Int, List<Int>> = mutableMapOf()
52 
53     // TODO(b/357060209): Add caching for this logic to improve efficiency.
54     val current: DesktopRepository
55         get() = desktopRepoByUserId.getOrCreate(userId)
56 
57     private val desktopRepoByUserId =
58         object : SparseArray<DesktopRepository>() {
59             /** Gets [DesktopRepository] for existing [userId] or creates a new one. */
60             fun getOrCreate(userId: Int): DesktopRepository =
61                 this[userId]
62                     ?: DesktopRepository(persistentRepository, mainCoroutineScope, userId).also {
63                         this[userId] = it
64                     }
65         }
66 
67     init {
68         userId = ActivityManager.getCurrentUser()
69         if (DesktopModeStatus.canEnterDesktopMode(context)) {
70             shellInit.addInitCallback(::onInit, this)
71         }
72         if (
73             ENABLE_DESKTOP_WINDOWING_HSUM.isTrue() &&
74                 !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
75         ) {
76             userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
77         }
78     }
79 
80     private fun onInit() {
81         repositoryInitializer.initialize(this)
82         shellController.addUserChangeListener(this)
83         if (
84             ENABLE_DESKTOP_WINDOWING_HSUM.isTrue() &&
85                 DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
86         ) {
87             userId = ActivityManager.getCurrentUser()
88             userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
89         }
90     }
91 
92     /** Returns [DesktopRepository] for the parent user id. */
93     fun getProfile(profileId: Int): DesktopRepository {
94         if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
95             for ((uid, profileIds) in userIdToProfileIdsMap) {
96                 if (profileId in profileIds) {
97                     return desktopRepoByUserId.getOrCreate(uid)
98                 }
99             }
100         }
101         return desktopRepoByUserId.getOrCreate(profileId)
102     }
103 
104     fun getUserIdForProfile(profileId: Int): Int {
105         if (userIdToProfileIdsMap[userId]?.contains(profileId) == true) return userId
106         else return profileId
107     }
108 
109     /** Dumps [DesktopRepository] for each user. */
110     fun dump(pw: PrintWriter, prefix: String) {
111         val innerPrefix = "$prefix    "
112         pw.println("${prefix}DesktopUserRepositories:")
113         pw.println("${innerPrefix}currentUserId=$userId")
114         desktopRepoByUserId.forEach { key, value -> value.dump(pw, innerPrefix) }
115     }
116 
117     override fun onUserChanged(newUserId: Int, userContext: Context) {
118         logD("onUserChanged previousUserId=%d, newUserId=%d", userId, newUserId)
119         userId = newUserId
120         if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
121             sanitizeUsers()
122         }
123     }
124 
125     override fun onUserProfilesChanged(profiles: MutableList<UserInfo>) {
126         logD("onUserProfilesChanged profiles=%s", profiles.toString())
127         if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
128             // TODO(b/366397912): Remove all persisted profile data when the profile changes.
129             userIdToProfileIdsMap[userId] = profiles.map { it.id }
130         }
131     }
132 
133     private fun sanitizeUsers() {
134         val aliveUserIds = userManager.getAliveUsers().map { it.id }
135         val usersToDelete = userIdToProfileIdsMap.keys.filterNot { it in aliveUserIds }
136 
137         usersToDelete.forEach { uid ->
138             userIdToProfileIdsMap.remove(uid)
139             desktopRepoByUserId.remove(uid)
140         }
141         mainCoroutineScope.launch {
142             try {
143                 persistentRepository.removeUsers(usersToDelete)
144             } catch (exception: Exception) {
145                 logE(
146                     "An exception occurred while updating the persistent repository \n%s",
147                     exception.stackTrace,
148                 )
149             }
150         }
151     }
152 
153     private fun logD(msg: String, vararg arguments: Any?) {
154         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
155     }
156 
157     private fun logE(msg: String, vararg arguments: Any?) {
158         ProtoLog.e(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
159     }
160 
161     companion object {
162         private const val TAG = "DesktopUserRepositories"
163     }
164 }
165