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.SuppressLint 21 import android.annotation.UserIdInt 22 import android.app.ActivityManager 23 import android.content.Context 24 import android.content.Intent 25 import android.content.IntentFilter 26 import android.content.pm.UserInfo 27 import android.graphics.drawable.BitmapDrawable 28 import android.graphics.drawable.Drawable 29 import android.graphics.drawable.Icon 30 import android.os.Process 31 import android.os.RemoteException 32 import android.os.UserHandle 33 import android.os.UserManager 34 import android.provider.Settings 35 import android.util.Log 36 import com.android.internal.logging.UiEventLogger 37 import com.android.internal.util.UserIcons 38 import com.android.keyguard.KeyguardUpdateMonitor 39 import com.android.keyguard.KeyguardUpdateMonitorCallback 40 import com.android.systemui.R 41 import com.android.systemui.SystemUISecondaryUserService 42 import com.android.systemui.animation.Expandable 43 import com.android.systemui.broadcast.BroadcastDispatcher 44 import com.android.systemui.common.shared.model.Text 45 import com.android.systemui.dagger.SysUISingleton 46 import com.android.systemui.dagger.qualifiers.Application 47 import com.android.systemui.dagger.qualifiers.Background 48 import com.android.systemui.flags.FeatureFlags 49 import com.android.systemui.flags.Flags 50 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor 51 import com.android.systemui.plugins.ActivityStarter 52 import com.android.systemui.qs.user.UserSwitchDialogController 53 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor 54 import com.android.systemui.user.CreateUserActivity 55 import com.android.systemui.user.data.model.UserSwitcherSettingsModel 56 import com.android.systemui.user.data.repository.UserRepository 57 import com.android.systemui.user.data.source.UserRecord 58 import com.android.systemui.user.domain.model.ShowDialogRequestModel 59 import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper 60 import com.android.systemui.user.shared.model.UserActionModel 61 import com.android.systemui.user.shared.model.UserModel 62 import com.android.systemui.user.utils.MultiUserActionsEvent 63 import com.android.systemui.user.utils.MultiUserActionsEventHelper 64 import com.android.systemui.util.kotlin.pairwise 65 import java.io.PrintWriter 66 import javax.inject.Inject 67 import kotlinx.coroutines.CoroutineDispatcher 68 import kotlinx.coroutines.CoroutineScope 69 import kotlinx.coroutines.flow.Flow 70 import kotlinx.coroutines.flow.MutableStateFlow 71 import kotlinx.coroutines.flow.SharingStarted 72 import kotlinx.coroutines.flow.StateFlow 73 import kotlinx.coroutines.flow.asStateFlow 74 import kotlinx.coroutines.flow.combine 75 import kotlinx.coroutines.flow.distinctUntilChanged 76 import kotlinx.coroutines.flow.launchIn 77 import kotlinx.coroutines.flow.map 78 import kotlinx.coroutines.flow.onEach 79 import kotlinx.coroutines.flow.stateIn 80 import kotlinx.coroutines.launch 81 import kotlinx.coroutines.sync.Mutex 82 import kotlinx.coroutines.sync.withLock 83 import kotlinx.coroutines.withContext 84 85 /** Encapsulates business logic to interact with user data and systems. */ 86 @SysUISingleton 87 class UserInteractor 88 @Inject 89 constructor( 90 @Application private val applicationContext: Context, 91 private val repository: UserRepository, 92 private val activityStarter: ActivityStarter, 93 private val keyguardInteractor: KeyguardInteractor, 94 private val featureFlags: FeatureFlags, 95 private val manager: UserManager, 96 private val headlessSystemUserMode: HeadlessSystemUserMode, 97 @Application private val applicationScope: CoroutineScope, 98 telephonyInteractor: TelephonyInteractor, 99 broadcastDispatcher: BroadcastDispatcher, 100 keyguardUpdateMonitor: KeyguardUpdateMonitor, 101 @Background private val backgroundDispatcher: CoroutineDispatcher, 102 private val activityManager: ActivityManager, 103 private val refreshUsersScheduler: RefreshUsersScheduler, 104 private val guestUserInteractor: GuestUserInteractor, 105 private val uiEventLogger: UiEventLogger, 106 ) { 107 /** 108 * Defines interface for classes that can be notified when the state of users on the device is 109 * changed. 110 */ 111 interface UserCallback { 112 /** Returns `true` if this callback can be cleaned-up. */ 113 fun isEvictable(): Boolean = false 114 115 /** Notifies that the state of users on the device has changed. */ 116 fun onUserStateChanged() 117 } 118 119 private val supervisedUserPackageName: String? 120 get() = 121 applicationContext.getString( 122 com.android.internal.R.string.config_supervisedUserCreationPackage 123 ) 124 125 private val callbackMutex = Mutex() 126 private val callbacks = mutableSetOf<UserCallback>() 127 private val userInfos: Flow<List<UserInfo>> = 128 repository.userInfos.map { userInfos -> userInfos.filter { it.isFull } } 129 130 /** List of current on-device users to select from. */ 131 val users: Flow<List<UserModel>> 132 get() = 133 combine( 134 userInfos, 135 repository.selectedUserInfo, 136 repository.userSwitcherSettings, 137 ) { userInfos, selectedUserInfo, settings -> 138 toUserModels( 139 userInfos = userInfos, 140 selectedUserId = selectedUserInfo.id, 141 isUserSwitcherEnabled = settings.isUserSwitcherEnabled, 142 ) 143 } 144 145 /** The currently-selected user. */ 146 val selectedUser: Flow<UserModel> 147 get() = 148 repository.selectedUserInfo.map { selectedUserInfo -> 149 val selectedUserId = selectedUserInfo.id 150 toUserModel( 151 userInfo = selectedUserInfo, 152 selectedUserId = selectedUserId, 153 canSwitchUsers = canSwitchUsers(selectedUserId) 154 ) 155 } 156 157 /** List of user-switcher related actions that are available. */ 158 val actions: Flow<List<UserActionModel>> 159 get() = 160 combine( 161 repository.selectedUserInfo, 162 userInfos, 163 repository.userSwitcherSettings, 164 keyguardInteractor.isKeyguardShowing, 165 ) { _, userInfos, settings, isDeviceLocked -> 166 buildList { 167 if (!isDeviceLocked || settings.isAddUsersFromLockscreen) { 168 // The device is locked and our setting to allow actions that add users 169 // from the lock-screen is not enabled. We can finish building the list 170 // here. 171 val isFullScreen = featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER) 172 173 val actionList: List<UserActionModel> = 174 if (isFullScreen) { 175 listOf( 176 UserActionModel.ADD_USER, 177 UserActionModel.ADD_SUPERVISED_USER, 178 UserActionModel.ENTER_GUEST_MODE, 179 ) 180 } else { 181 listOf( 182 UserActionModel.ENTER_GUEST_MODE, 183 UserActionModel.ADD_USER, 184 UserActionModel.ADD_SUPERVISED_USER, 185 ) 186 } 187 actionList.map { 188 when (it) { 189 UserActionModel.ENTER_GUEST_MODE -> { 190 val hasGuestUser = userInfos.any { it.isGuest } 191 if (!hasGuestUser && canCreateGuestUser(settings)) { 192 add(UserActionModel.ENTER_GUEST_MODE) 193 } 194 } 195 UserActionModel.ADD_USER -> { 196 val canCreateUsers = 197 UserActionsUtil.canCreateUser( 198 manager, 199 repository, 200 settings.isUserSwitcherEnabled, 201 settings.isAddUsersFromLockscreen, 202 ) 203 204 if (canCreateUsers) { 205 add(UserActionModel.ADD_USER) 206 } 207 } 208 UserActionModel.ADD_SUPERVISED_USER -> { 209 if ( 210 UserActionsUtil.canCreateSupervisedUser( 211 manager, 212 repository, 213 settings.isUserSwitcherEnabled, 214 settings.isAddUsersFromLockscreen, 215 supervisedUserPackageName, 216 ) 217 ) { 218 add(UserActionModel.ADD_SUPERVISED_USER) 219 } 220 } 221 else -> Unit 222 } 223 } 224 } 225 if ( 226 UserActionsUtil.canManageUsers( 227 repository, 228 settings.isUserSwitcherEnabled, 229 settings.isAddUsersFromLockscreen, 230 ) 231 ) { 232 add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT) 233 } 234 } 235 } 236 237 val userRecords: StateFlow<ArrayList<UserRecord>> = 238 combine( 239 userInfos, 240 repository.selectedUserInfo, 241 actions, 242 repository.userSwitcherSettings, 243 ) { userInfos, selectedUserInfo, actionModels, settings -> 244 ArrayList( 245 userInfos.map { 246 toRecord( 247 userInfo = it, 248 selectedUserId = selectedUserInfo.id, 249 ) 250 } + 251 actionModels.map { 252 toRecord( 253 action = it, 254 selectedUserId = selectedUserInfo.id, 255 isRestricted = 256 it != UserActionModel.ENTER_GUEST_MODE && 257 it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT && 258 !settings.isAddUsersFromLockscreen, 259 ) 260 } 261 ) 262 } 263 .onEach { notifyCallbacks() } 264 .stateIn( 265 scope = applicationScope, 266 started = SharingStarted.Eagerly, 267 initialValue = ArrayList(), 268 ) 269 270 val selectedUserRecord: StateFlow<UserRecord?> = 271 repository.selectedUserInfo 272 .map { selectedUserInfo -> 273 toRecord(userInfo = selectedUserInfo, selectedUserId = selectedUserInfo.id) 274 } 275 .stateIn( 276 scope = applicationScope, 277 started = SharingStarted.Eagerly, 278 initialValue = null, 279 ) 280 281 /** Whether the device is configured to always have a guest user available. */ 282 val isGuestUserAutoCreated: Boolean = guestUserInteractor.isGuestUserAutoCreated 283 284 /** Whether the guest user is currently being reset. */ 285 val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting 286 287 /** Whether to enable the user chip in the status bar */ 288 val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled 289 290 private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null) 291 val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow() 292 293 private val _dialogDismissRequests = MutableStateFlow<Unit?>(null) 294 val dialogDismissRequests: Flow<Unit?> = _dialogDismissRequests.asStateFlow() 295 296 val isSimpleUserSwitcher: Boolean 297 get() = repository.isSimpleUserSwitcher() 298 299 val isUserSwitcherEnabled: Boolean 300 get() = repository.isUserSwitcherEnabled() 301 302 val keyguardUpdateMonitorCallback = 303 object : KeyguardUpdateMonitorCallback() { 304 override fun onKeyguardGoingAway() { 305 dismissDialog() 306 } 307 } 308 309 init { 310 refreshUsersScheduler.refreshIfNotPaused() 311 telephonyInteractor.callState 312 .distinctUntilChanged() 313 .onEach { refreshUsersScheduler.refreshIfNotPaused() } 314 .launchIn(applicationScope) 315 316 combine( 317 broadcastDispatcher.broadcastFlow( 318 filter = 319 IntentFilter().apply { 320 addAction(Intent.ACTION_USER_ADDED) 321 addAction(Intent.ACTION_USER_REMOVED) 322 addAction(Intent.ACTION_USER_INFO_CHANGED) 323 addAction(Intent.ACTION_USER_SWITCHED) 324 addAction(Intent.ACTION_USER_STOPPED) 325 addAction(Intent.ACTION_USER_UNLOCKED) 326 }, 327 user = UserHandle.SYSTEM, 328 map = { intent, _ -> intent }, 329 ), 330 repository.selectedUserInfo.pairwise(null), 331 ) { intent, selectedUserChange -> 332 Pair(intent, selectedUserChange.previousValue) 333 } 334 .onEach { (intent, previousSelectedUser) -> 335 onBroadcastReceived(intent, previousSelectedUser) 336 } 337 .launchIn(applicationScope) 338 restartSecondaryService(repository.getSelectedUserInfo().id) 339 keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) 340 } 341 342 fun addCallback(callback: UserCallback) { 343 applicationScope.launch { callbackMutex.withLock { callbacks.add(callback) } } 344 } 345 346 fun removeCallback(callback: UserCallback) { 347 applicationScope.launch { callbackMutex.withLock { callbacks.remove(callback) } } 348 } 349 350 fun refreshUsers() { 351 refreshUsersScheduler.refreshIfNotPaused() 352 } 353 354 fun onDialogShown() { 355 _dialogShowRequests.value = null 356 } 357 358 fun onDialogDismissed() { 359 _dialogDismissRequests.value = null 360 } 361 362 fun dump(pw: PrintWriter) { 363 pw.println("UserInteractor state:") 364 pw.println(" lastSelectedNonGuestUserId=${repository.lastSelectedNonGuestUserId}") 365 366 val users = userRecords.value.filter { it.info != null } 367 pw.println(" userCount=${userRecords.value.count { LegacyUserDataHelper.isUser(it) }}") 368 for (i in users.indices) { 369 pw.println(" ${users[i]}") 370 } 371 372 val actions = userRecords.value.filter { it.info == null } 373 pw.println(" actionCount=${userRecords.value.count { !LegacyUserDataHelper.isUser(it) }}") 374 for (i in actions.indices) { 375 pw.println(" ${actions[i]}") 376 } 377 378 pw.println("isSimpleUserSwitcher=$isSimpleUserSwitcher") 379 pw.println("isUserSwitcherEnabled=$isUserSwitcherEnabled") 380 pw.println("isGuestUserAutoCreated=$isGuestUserAutoCreated") 381 } 382 383 fun onDeviceBootCompleted() { 384 guestUserInteractor.onDeviceBootCompleted() 385 } 386 387 /** Switches to the user or executes the action represented by the given record. */ 388 fun onRecordSelected( 389 record: UserRecord, 390 dialogShower: UserSwitchDialogController.DialogShower? = null, 391 ) { 392 if (LegacyUserDataHelper.isUser(record)) { 393 // It's safe to use checkNotNull around record.info because isUser only returns true 394 // if record.info is not null. 395 uiEventLogger.log( 396 MultiUserActionsEventHelper.userSwitchMetric(checkNotNull(record.info)) 397 ) 398 selectUser(checkNotNull(record.info).id, dialogShower) 399 } else { 400 executeAction(LegacyUserDataHelper.toUserActionModel(record), dialogShower) 401 } 402 } 403 404 /** Switches to the user with the given user ID. */ 405 fun selectUser( 406 newlySelectedUserId: Int, 407 dialogShower: UserSwitchDialogController.DialogShower? = null, 408 ) { 409 val currentlySelectedUserInfo = repository.getSelectedUserInfo() 410 if ( 411 newlySelectedUserId == currentlySelectedUserInfo.id && currentlySelectedUserInfo.isGuest 412 ) { 413 // Here when clicking on the currently-selected guest user to leave guest mode 414 // and return to the previously-selected non-guest user. 415 showDialog( 416 ShowDialogRequestModel.ShowExitGuestDialog( 417 guestUserId = currentlySelectedUserInfo.id, 418 targetUserId = repository.lastSelectedNonGuestUserId, 419 isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, 420 isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), 421 onExitGuestUser = this::exitGuestUser, 422 dialogShower = dialogShower, 423 ) 424 ) 425 return 426 } 427 428 if (currentlySelectedUserInfo.isGuest) { 429 // Here when switching from guest to a non-guest user. 430 showDialog( 431 ShowDialogRequestModel.ShowExitGuestDialog( 432 guestUserId = currentlySelectedUserInfo.id, 433 targetUserId = newlySelectedUserId, 434 isGuestEphemeral = currentlySelectedUserInfo.isEphemeral, 435 isKeyguardShowing = keyguardInteractor.isKeyguardShowing(), 436 onExitGuestUser = this::exitGuestUser, 437 dialogShower = dialogShower, 438 ) 439 ) 440 return 441 } 442 443 dialogShower?.dismiss() 444 445 switchUser(newlySelectedUserId) 446 } 447 448 /** Executes the given action. */ 449 fun executeAction( 450 action: UserActionModel, 451 dialogShower: UserSwitchDialogController.DialogShower? = null, 452 ) { 453 when (action) { 454 UserActionModel.ENTER_GUEST_MODE -> { 455 uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER) 456 guestUserInteractor.createAndSwitchTo( 457 this::showDialog, 458 this::dismissDialog, 459 ) { userId -> 460 selectUser(userId, dialogShower) 461 } 462 } 463 UserActionModel.ADD_USER -> { 464 uiEventLogger.log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER) 465 val currentUser = repository.getSelectedUserInfo() 466 dismissDialog() 467 activityStarter.startActivity( 468 CreateUserActivity.createIntentForStart( 469 applicationContext, 470 keyguardInteractor.isKeyguardShowing() 471 ), 472 /* dismissShade= */ true, 473 /* animationController */ null, 474 /* showOverLockscreenWhenLocked */ true, 475 /* userHandle */ currentUser.getUserHandle(), 476 ) 477 } 478 UserActionModel.ADD_SUPERVISED_USER -> { 479 uiEventLogger.log(MultiUserActionsEvent.CREATE_RESTRICTED_USER_FROM_USER_SWITCHER) 480 dismissDialog() 481 activityStarter.startActivity( 482 Intent() 483 .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER) 484 .setPackage(supervisedUserPackageName) 485 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 486 /* dismissShade= */ true, 487 ) 488 } 489 UserActionModel.NAVIGATE_TO_USER_MANAGEMENT -> 490 activityStarter.startActivity( 491 Intent(Settings.ACTION_USER_SETTINGS), 492 /* dismissShade= */ true, 493 ) 494 } 495 } 496 497 fun exitGuestUser( 498 @UserIdInt guestUserId: Int, 499 @UserIdInt targetUserId: Int, 500 forceRemoveGuestOnExit: Boolean, 501 ) { 502 guestUserInteractor.exit( 503 guestUserId = guestUserId, 504 targetUserId = targetUserId, 505 forceRemoveGuestOnExit = forceRemoveGuestOnExit, 506 showDialog = this::showDialog, 507 dismissDialog = this::dismissDialog, 508 switchUser = this::switchUser, 509 ) 510 } 511 512 fun removeGuestUser( 513 @UserIdInt guestUserId: Int, 514 @UserIdInt targetUserId: Int, 515 ) { 516 applicationScope.launch { 517 guestUserInteractor.remove( 518 guestUserId = guestUserId, 519 targetUserId = targetUserId, 520 ::showDialog, 521 ::dismissDialog, 522 ::selectUser, 523 ) 524 } 525 } 526 527 fun showUserSwitcher(expandable: Expandable) { 528 if (featureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) { 529 showDialog(ShowDialogRequestModel.ShowUserSwitcherFullscreenDialog(expandable)) 530 } else { 531 showDialog(ShowDialogRequestModel.ShowUserSwitcherDialog(expandable)) 532 } 533 } 534 535 /** Returns the ID of the currently-selected user. */ 536 @UserIdInt 537 fun getSelectedUserId(): Int { 538 return repository.getSelectedUserInfo().id 539 } 540 541 private fun showDialog(request: ShowDialogRequestModel) { 542 _dialogShowRequests.value = request 543 } 544 545 private fun dismissDialog() { 546 _dialogDismissRequests.value = Unit 547 } 548 549 private fun notifyCallbacks() { 550 applicationScope.launch { 551 callbackMutex.withLock { 552 val iterator = callbacks.iterator() 553 while (iterator.hasNext()) { 554 val callback = iterator.next() 555 if (!callback.isEvictable()) { 556 callback.onUserStateChanged() 557 } else { 558 iterator.remove() 559 } 560 } 561 } 562 } 563 } 564 565 private suspend fun toRecord( 566 userInfo: UserInfo, 567 selectedUserId: Int, 568 ): UserRecord { 569 return LegacyUserDataHelper.createRecord( 570 context = applicationContext, 571 manager = manager, 572 userInfo = userInfo, 573 picture = null, 574 isCurrent = userInfo.id == selectedUserId, 575 canSwitchUsers = canSwitchUsers(selectedUserId), 576 ) 577 } 578 579 private suspend fun toRecord( 580 action: UserActionModel, 581 selectedUserId: Int, 582 isRestricted: Boolean, 583 ): UserRecord { 584 return LegacyUserDataHelper.createRecord( 585 context = applicationContext, 586 selectedUserId = selectedUserId, 587 actionType = action, 588 isRestricted = isRestricted, 589 isSwitchToEnabled = 590 canSwitchUsers( 591 selectedUserId = selectedUserId, 592 isAction = true, 593 ) && 594 // If the user is auto-created is must not be currently resetting. 595 !(isGuestUserAutoCreated && isGuestUserResetting), 596 ) 597 } 598 599 private fun switchUser(userId: Int) { 600 // TODO(b/246631653): track jank and latency like in the old impl. 601 refreshUsersScheduler.pause() 602 try { 603 activityManager.switchUser(userId) 604 } catch (e: RemoteException) { 605 Log.e(TAG, "Couldn't switch user.", e) 606 } 607 } 608 609 private suspend fun onBroadcastReceived( 610 intent: Intent, 611 previousUserInfo: UserInfo?, 612 ) { 613 val shouldRefreshAllUsers = 614 when (intent.action) { 615 Intent.ACTION_USER_SWITCHED -> { 616 dismissDialog() 617 val selectedUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1) 618 if (previousUserInfo?.id != selectedUserId) { 619 notifyCallbacks() 620 restartSecondaryService(selectedUserId) 621 } 622 if (guestUserInteractor.isGuestUserAutoCreated) { 623 guestUserInteractor.guaranteePresent() 624 } 625 true 626 } 627 Intent.ACTION_USER_INFO_CHANGED -> true 628 Intent.ACTION_USER_UNLOCKED -> { 629 // If we unlocked the system user, we should refresh all users. 630 intent.getIntExtra( 631 Intent.EXTRA_USER_HANDLE, 632 UserHandle.USER_NULL, 633 ) == UserHandle.USER_SYSTEM 634 } 635 else -> true 636 } 637 638 if (shouldRefreshAllUsers) { 639 refreshUsersScheduler.unpauseAndRefresh() 640 } 641 } 642 643 private fun restartSecondaryService(@UserIdInt userId: Int) { 644 // Do not start service for user that is marked for deletion. 645 if (!manager.aliveUsers.map { it.id }.contains(userId)) { 646 return 647 } 648 649 val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java) 650 // Disconnect from the old secondary user's service 651 val secondaryUserId = repository.secondaryUserId 652 if (secondaryUserId != UserHandle.USER_NULL) { 653 applicationContext.stopServiceAsUser( 654 intent, 655 UserHandle.of(secondaryUserId), 656 ) 657 repository.secondaryUserId = UserHandle.USER_NULL 658 } 659 660 // Connect to the new secondary user's service (purely to ensure that a persistent 661 // SystemUI application is created for that user) 662 663 if (userId != Process.myUserHandle().identifier) { 664 applicationContext.startServiceAsUser( 665 intent, 666 UserHandle.of(userId), 667 ) 668 repository.secondaryUserId = userId 669 } 670 } 671 672 private suspend fun toUserModels( 673 userInfos: List<UserInfo>, 674 selectedUserId: Int, 675 isUserSwitcherEnabled: Boolean, 676 ): List<UserModel> { 677 val canSwitchUsers = canSwitchUsers(selectedUserId) 678 679 return userInfos 680 // The guest user should go in the last position. 681 .sortedBy { it.isGuest } 682 .mapNotNull { userInfo -> 683 filterAndMapToUserModel( 684 userInfo = userInfo, 685 selectedUserId = selectedUserId, 686 canSwitchUsers = canSwitchUsers, 687 isUserSwitcherEnabled = isUserSwitcherEnabled, 688 ) 689 } 690 } 691 692 /** 693 * Maps UserInfo to UserModel based on some parameters and return null under certain conditions 694 * to be filtered out. 695 */ 696 private suspend fun filterAndMapToUserModel( 697 userInfo: UserInfo, 698 selectedUserId: Int, 699 canSwitchUsers: Boolean, 700 isUserSwitcherEnabled: Boolean, 701 ): UserModel? { 702 return when { 703 // When the user switcher is not enabled in settings, we only show the primary user. 704 !isUserSwitcherEnabled && !userInfo.isPrimary -> null 705 // We avoid showing disabled users. 706 !userInfo.isEnabled -> null 707 // We meet the conditions to return the UserModel. 708 userInfo.isGuest || userInfo.supportsSwitchToByUser() -> 709 toUserModel(userInfo, selectedUserId, canSwitchUsers) 710 else -> null 711 } 712 } 713 714 /** Maps UserInfo to UserModel based on some parameters. */ 715 private suspend fun toUserModel( 716 userInfo: UserInfo, 717 selectedUserId: Int, 718 canSwitchUsers: Boolean 719 ): UserModel { 720 val userId = userInfo.id 721 val isSelected = userId == selectedUserId 722 return if (userInfo.isGuest) { 723 UserModel( 724 id = userId, 725 name = Text.Loaded(userInfo.name), 726 image = 727 getUserImage( 728 isGuest = true, 729 userId = userId, 730 ), 731 isSelected = isSelected, 732 isSelectable = canSwitchUsers, 733 isGuest = true, 734 ) 735 } else { 736 UserModel( 737 id = userId, 738 name = Text.Loaded(userInfo.name), 739 image = 740 getUserImage( 741 isGuest = false, 742 userId = userId, 743 ), 744 isSelected = isSelected, 745 isSelectable = canSwitchUsers || isSelected, 746 isGuest = false, 747 ) 748 } 749 } 750 751 private suspend fun canSwitchUsers( 752 selectedUserId: Int, 753 isAction: Boolean = false, 754 ): Boolean { 755 val isHeadlessSystemUserMode = 756 withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() } 757 // Whether menu item should be active. True if item is a user or if any user has 758 // signed in since reboot or in all cases for non-headless system user mode. 759 val isItemEnabled = !isAction || !isHeadlessSystemUserMode || isAnyUserUnlocked() 760 return isItemEnabled && 761 withContext(backgroundDispatcher) { 762 manager.getUserSwitchability(UserHandle.of(selectedUserId)) 763 } == UserManager.SWITCHABILITY_STATUS_OK 764 } 765 766 private suspend fun isAnyUserUnlocked(): Boolean { 767 return manager 768 .getUsers( 769 /* excludePartial= */ true, 770 /* excludeDying= */ true, 771 /* excludePreCreated= */ true 772 ) 773 .any { user -> 774 user.id != UserHandle.USER_SYSTEM && 775 withContext(backgroundDispatcher) { manager.isUserUnlocked(user.userHandle) } 776 } 777 } 778 779 @SuppressLint("UseCompatLoadingForDrawables") 780 private suspend fun getUserImage( 781 isGuest: Boolean, 782 userId: Int, 783 ): Drawable { 784 if (isGuest) { 785 return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle)) 786 } 787 788 // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them. 789 val userIcon = 790 withContext(backgroundDispatcher) { 791 manager.getUserIcon(userId)?.let { bitmap -> 792 val iconSize = 793 applicationContext.resources.getDimensionPixelSize( 794 R.dimen.bouncer_user_switcher_icon_size 795 ) 796 Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize) 797 } 798 } 799 800 if (userIcon != null) { 801 return BitmapDrawable(userIcon) 802 } 803 804 return UserIcons.getDefaultUserIcon( 805 applicationContext.resources, 806 userId, 807 /* light= */ false 808 ) 809 } 810 811 private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean { 812 return guestUserInteractor.isGuestUserAutoCreated || 813 UserActionsUtil.canCreateGuest( 814 manager, 815 repository, 816 settings.isUserSwitcherEnabled, 817 settings.isAddUsersFromLockscreen, 818 ) 819 } 820 821 companion object { 822 private const val TAG = "UserInteractor" 823 } 824 } 825