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