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