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 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 = UserHandle.USER_SYSTEM 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 != UserHandle.USER_SYSTEM) { 147 val info = 148 withContext(backgroundDispatcher) { 149 manager.getUserInfo(lastSelectedNonGuestUserHandle) 150 } 151 if (info != null && info.isEnabled && info.supportsSwitchToByUser()) { 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 system user") 219 switchUser(UserHandle.USER_SYSTEM) 220 withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) } 221 try { 222 WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null) 223 } catch (e: RemoteException) { 224 Log.e( 225 TAG, 226 "Couldn't remove guest because ActivityManager or WindowManager is dead" 227 ) 228 } 229 return 230 } 231 232 switchUser(newGuestId) 233 234 withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) } 235 } else { 236 if (repository.isGuestUserAutoCreated) { 237 repository.isGuestUserResetting = true 238 } 239 switchUser(targetUserId) 240 manager.removeUser(currentUser.id) 241 } 242 } 243 244 /** 245 * Creates the guest user and adds it to the device. 246 * 247 * @param showDialog A function to invoke to show a dialog. 248 * @param dismissDialog A function to invoke to dismiss a dialog. 249 * @return The user ID of the newly-created guest user. 250 */ 251 private suspend fun create( 252 showDialog: (ShowDialogRequestModel) -> Unit, 253 dismissDialog: () -> Unit, 254 ): Int { 255 return withContext(mainDispatcher) { 256 showDialog(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true)) 257 val guestUserId = createInBackground() 258 dismissDialog() 259 if (guestUserId != UserHandle.USER_NULL) { 260 uiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_ADD) 261 } else { 262 Toast.makeText( 263 applicationContext, 264 com.android.settingslib.R.string.add_guest_failed, 265 Toast.LENGTH_SHORT, 266 ) 267 .show() 268 } 269 270 guestUserId 271 } 272 } 273 274 /** Schedules the creation of the guest user. */ 275 private suspend fun scheduleCreation() { 276 if (!repository.isGuestUserCreationScheduled.compareAndSet(false, true)) { 277 return 278 } 279 280 withContext(backgroundDispatcher) { 281 val newGuestUserId = createInBackground() 282 repository.isGuestUserCreationScheduled.set(false) 283 repository.isGuestUserResetting = false 284 if (newGuestUserId == UserHandle.USER_NULL) { 285 Log.w(TAG, "Could not create new guest while exiting existing guest") 286 // Refresh users so that we still display "Guest" if 287 // config_guestUserAutoCreated=true 288 refreshUsersScheduler.refreshIfNotPaused() 289 } 290 } 291 } 292 293 /** 294 * Creates a guest user and return its multi-user user ID. 295 * 296 * This method does not check if a guest already exists before it makes a call to [UserManager] 297 * to create a new one. 298 * 299 * @return The multi-user user ID of the newly created guest user, or [UserHandle.USER_NULL] if 300 * the guest couldn't be created. 301 */ 302 @UserIdInt 303 private suspend fun createInBackground(): Int { 304 return withContext(backgroundDispatcher) { 305 try { 306 val guestUser = manager.createGuest(applicationContext) 307 if (guestUser != null) { 308 guestUser.id 309 } else { 310 Log.e( 311 TAG, 312 "Couldn't create guest, most likely because there already exists one!" 313 ) 314 UserHandle.USER_NULL 315 } 316 } catch (e: UserManager.UserOperationException) { 317 Log.e(TAG, "Couldn't create guest user!", e) 318 UserHandle.USER_NULL 319 } 320 } 321 } 322 323 private fun isDeviceAllowedToAddGuest(): Boolean { 324 return deviceProvisionedController.isDeviceProvisioned && 325 !devicePolicyManager.isDeviceManaged 326 } 327 328 companion object { 329 private const val TAG = "GuestUserInteractor" 330 } 331 } 332