1 /* <lambda>null2 * Copyright (C) 2022 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 18 package com.android.systemui.user.domain.interactor 19 20 import android.annotation.UserIdInt 21 import android.app.admin.DevicePolicyManager 22 import android.content.Context 23 import android.content.pm.UserInfo 24 import android.os.RemoteException 25 import android.os.UserHandle 26 import android.os.UserManager 27 import android.util.Log 28 import android.view.WindowManagerGlobal 29 import android.widget.Toast 30 import com.android.internal.logging.UiEventLogger 31 import com.android.systemui.GuestResetOrExitSessionReceiver 32 import com.android.systemui.GuestResumeSessionReceiver 33 import com.android.systemui.dagger.SysUISingleton 34 import com.android.systemui.dagger.qualifiers.Application 35 import com.android.systemui.dagger.qualifiers.Background 36 import com.android.systemui.dagger.qualifiers.Main 37 import com.android.systemui.qs.QSUserSwitcherEvent 38 import com.android.systemui.statusbar.policy.DeviceProvisionedController 39 import com.android.systemui.user.data.repository.UserRepository 40 import com.android.systemui.user.domain.model.ShowDialogRequestModel 41 import javax.inject.Inject 42 import kotlinx.coroutines.CoroutineDispatcher 43 import kotlinx.coroutines.CoroutineScope 44 import kotlinx.coroutines.launch 45 import kotlinx.coroutines.suspendCancellableCoroutine 46 import kotlinx.coroutines.withContext 47 48 /** Encapsulates business logic to interact with guest user data and systems. */ 49 @SysUISingleton 50 class GuestUserInteractor 51 @Inject 52 constructor( 53 @Application private val applicationContext: Context, 54 @Application private val applicationScope: CoroutineScope, 55 @Main private val mainDispatcher: CoroutineDispatcher, 56 @Background private val backgroundDispatcher: CoroutineDispatcher, 57 private val manager: UserManager, 58 private val repository: UserRepository, 59 private val deviceProvisionedController: DeviceProvisionedController, 60 private val devicePolicyManager: DevicePolicyManager, 61 private val refreshUsersScheduler: RefreshUsersScheduler, 62 private val uiEventLogger: UiEventLogger, 63 resumeSessionReceiver: GuestResumeSessionReceiver, 64 resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver, 65 ) { 66 /** Whether the device is configured to always have a guest user available. */ 67 val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated 68 69 /** Whether the guest user is currently being reset. */ 70 val isGuestUserResetting: Boolean = repository.isGuestUserResetting 71 72 init { 73 if (applicationContext.userId == UserHandle.USER_SYSTEM) { 74 resumeSessionReceiver.register() 75 resetOrExitSessionReceiver.register() 76 } 77 } 78 79 /** Notifies that the device has finished booting. */ 80 fun onDeviceBootCompleted() { 81 applicationScope.launch { 82 if (isDeviceAllowedToAddGuest()) { 83 guaranteePresent() 84 return@launch 85 } 86 87 suspendCancellableCoroutine<Unit> { continuation -> 88 val callback = 89 object : DeviceProvisionedController.DeviceProvisionedListener { 90 override fun onDeviceProvisionedChanged() { 91 continuation.resumeWith(Result.success(Unit)) 92 deviceProvisionedController.removeCallback(this) 93 } 94 } 95 96 deviceProvisionedController.addCallback(callback) 97 } 98 99 if (isDeviceAllowedToAddGuest()) { 100 guaranteePresent() 101 } 102 } 103 } 104 105 /** Creates a guest user and switches to it. */ 106 fun createAndSwitchTo( 107 showDialog: (ShowDialogRequestModel) -> Unit, 108 dismissDialog: () -> Unit, 109 selectUser: (userId: Int) -> Unit, 110 ) { 111 applicationScope.launch { 112 val newGuestUserId = create(showDialog, dismissDialog) 113 if (newGuestUserId != UserHandle.USER_NULL) { 114 selectUser(newGuestUserId) 115 } 116 } 117 } 118 119 /** Exits the guest user, switching back to the last non-guest user or to the default user. */ 120 fun exit( 121 @UserIdInt guestUserId: Int, 122 @UserIdInt targetUserId: Int, 123 forceRemoveGuestOnExit: Boolean, 124 showDialog: (ShowDialogRequestModel) -> Unit, 125 dismissDialog: () -> Unit, 126 switchUser: (userId: Int) -> Unit, 127 ) { 128 val currentUserInfo = repository.getSelectedUserInfo() 129 if (currentUserInfo.id != guestUserId) { 130 Log.w( 131 TAG, 132 "User requesting to start a new session ($guestUserId) is not current user" + 133 " (${currentUserInfo.id})" 134 ) 135 return 136 } 137 138 if (!currentUserInfo.isGuest) { 139 Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest") 140 return 141 } 142 143 applicationScope.launch { 144 var newUserId = repository.mainUserId 145 if (targetUserId == UserHandle.USER_NULL) { 146 // When a target user is not specified switch to last non guest user: 147 val lastSelectedNonGuestUserHandle = repository.lastSelectedNonGuestUserId 148 if (lastSelectedNonGuestUserHandle != repository.mainUserId) { 149 val info = 150 withContext(backgroundDispatcher) { 151 manager.getUserInfo(lastSelectedNonGuestUserHandle) 152 } 153 if (info != null && info.isEnabled && info.supportsSwitchTo()) { 154 newUserId = info.id 155 } 156 } 157 } else { 158 newUserId = targetUserId 159 } 160 161 if (currentUserInfo.isEphemeral || forceRemoveGuestOnExit) { 162 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE) 163 remove(currentUserInfo.id, newUserId, showDialog, dismissDialog, switchUser) 164 } else { 165 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH) 166 switchUser(newUserId) 167 } 168 } 169 } 170 171 /** 172 * Guarantees that the guest user is present on the device, creating it if needed and if allowed 173 * to. 174 */ 175 suspend fun guaranteePresent() { 176 if (!isDeviceAllowedToAddGuest()) { 177 return 178 } 179 180 val guestUser = withContext(backgroundDispatcher) { manager.findCurrentGuestUser() } 181 if (guestUser == null) { 182 scheduleCreation() 183 } 184 } 185 186 /** Removes the guest user from the device. */ 187 suspend fun remove( 188 @UserIdInt guestUserId: Int, 189 @UserIdInt targetUserId: Int, 190 showDialog: (ShowDialogRequestModel) -> Unit, 191 dismissDialog: () -> Unit, 192 switchUser: (userId: Int) -> Unit, 193 ) { 194 val currentUser: UserInfo = repository.getSelectedUserInfo() 195 if (currentUser.id != guestUserId) { 196 Log.w( 197 TAG, 198 "User requesting to start a new session ($guestUserId) is not current user" + 199 " ($currentUser.id)" 200 ) 201 return 202 } 203 204 if (!currentUser.isGuest) { 205 Log.w(TAG, "User requesting to start a new session ($guestUserId) is not a guest") 206 return 207 } 208 209 val marked = 210 withContext(backgroundDispatcher) { manager.markGuestForDeletion(currentUser.id) } 211 if (!marked) { 212 Log.w(TAG, "Couldn't mark the guest for deletion for user $guestUserId") 213 return 214 } 215 216 if (targetUserId == UserHandle.USER_NULL) { 217 // Create a new guest in the foreground, and then immediately switch to it 218 val newGuestId = create(showDialog, dismissDialog) 219 if (newGuestId == UserHandle.USER_NULL) { 220 Log.e(TAG, "Could not create new guest, switching back to main user") 221 val mainUser = withContext(backgroundDispatcher) { manager.mainUser?.identifier } 222 223 mainUser?.let { switchUser(it) } 224 225 withContext(backgroundDispatcher) { 226 manager.removeUserWhenPossible( 227 UserHandle.of(currentUser.id), 228 /* overrideDevicePolicy= */ false 229 ) 230 } 231 try { 232 checkNotNull(WindowManagerGlobal.getWindowManagerService()) 233 .lockNow(/* options= */ null) 234 } catch (e: RemoteException) { 235 Log.e( 236 TAG, 237 "Couldn't remove guest because ActivityManager or WindowManager is dead" 238 ) 239 } 240 return 241 } 242 243 switchUser(newGuestId) 244 245 withContext(backgroundDispatcher) { 246 manager.removeUserWhenPossible( 247 UserHandle.of(currentUser.id), 248 /* overrideDevicePolicy= */ false 249 ) 250 } 251 } else { 252 if (repository.isGuestUserAutoCreated) { 253 repository.isGuestUserResetting = true 254 } 255 switchUser(targetUserId) 256 manager.removeUserWhenPossible( 257 UserHandle.of(currentUser.id), 258 /* overrideDevicePolicy= */ false 259 ) 260 } 261 } 262 263 /** 264 * Creates the guest user and adds it to the device. 265 * 266 * @param showDialog A function to invoke to show a dialog. 267 * @param dismissDialog A function to invoke to dismiss a dialog. 268 * @return The user ID of the newly-created guest user. 269 */ 270 private suspend fun create( 271 showDialog: (ShowDialogRequestModel) -> Unit, 272 dismissDialog: () -> Unit, 273 ): Int { 274 return withContext(mainDispatcher) { 275 showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true)) 276 val guestUserId = createInBackground() 277 dismissDialog() 278 if (guestUserId != UserHandle.USER_NULL) { 279 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD) 280 } else { 281 Toast.makeText( 282 applicationContext, 283 com.android.settingslib.R.string.add_guest_failed, 284 Toast.LENGTH_SHORT, 285 ) 286 .show() 287 } 288 289 guestUserId 290 } 291 } 292 293 /** Schedules the creation of the guest user. */ 294 private suspend fun scheduleCreation() { 295 if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) { 296 return 297 } 298 299 withContext(backgroundDispatcher) { 300 val newGuestUserId = createInBackground() 301 repository.isGuestUserCreationScheduled.set(false) 302 repository.isGuestUserResetting = false 303 if (newGuestUserId == UserHandle.USER_NULL) { 304 Log.w(TAG, "Could not create new guest while exiting existing guest") 305 // Refresh users so that we still display "Guest" if 306 // config_guestUserAutoCreated=true 307 refreshUsersScheduler.refreshIfNotPaused() 308 } 309 } 310 } 311 312 /** 313 * Creates a guest user and return its multi-user user ID. 314 * 315 * This method does not check if a guest already exists before it makes a call to [UserManager] 316 * to create a new one. 317 * 318 * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if 319 * the guest couldn't be created. 320 */ 321 @UserIdInt 322 private suspend fun createInBackground(): Int { 323 return withContext(backgroundDispatcher) { 324 try { 325 val guestUser = manager.createGuest(applicationContext) 326 if (guestUser != null) { 327 guestUser.id 328 } else { 329 Log.e( 330 TAG, 331 "Couldn't create guest, most likely because there already exists one!" 332 ) 333 UserHandle.USER_NULL 334 } 335 } catch (e: UserManager.UserOperationException) { 336 Log.e(TAG, "Couldn't create guest user!", e) 337 UserHandle.USER_NULL 338 } 339 } 340 } 341 342 private fun isDeviceAllowedToAddGuest(): Boolean { 343 return deviceProvisionedController.isDeviceProvisioned && 344 !devicePolicyManager.isDeviceManaged 345 } 346 347 companion object { 348 private const val TAG = "GuestUserInteractor" 349 } 350 } 351