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.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 val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java) 645 // Disconnect from the old secondary user's service 646 val secondaryUserId = repository.secondaryUserId 647 if (secondaryUserId != UserHandle.USER_NULL) { 648 applicationContext.stopServiceAsUser( 649 intent, 650 UserHandle.of(secondaryUserId), 651 ) 652 repository.secondaryUserId = UserHandle.USER_NULL 653 } 654 655 // Connect to the new secondary user's service (purely to ensure that a persistent 656 // SystemUI application is created for that user) 657 if (userId != Process.myUserHandle().identifier) { 658 applicationContext.startServiceAsUser( 659 intent, 660 UserHandle.of(userId), 661 ) 662 repository.secondaryUserId = userId 663 } 664 } 665 666 private suspend fun toUserModels( 667 userInfos: List<UserInfo>, 668 selectedUserId: Int, 669 isUserSwitcherEnabled: Boolean, 670 ): List<UserModel> { 671 val canSwitchUsers = canSwitchUsers(selectedUserId) 672 673 return userInfos 674 // The guest user should go in the last position. 675 .sortedBy { it.isGuest } 676 .mapNotNull { userInfo -> 677 filterAndMapToUserModel( 678 userInfo = userInfo, 679 selectedUserId = selectedUserId, 680 canSwitchUsers = canSwitchUsers, 681 isUserSwitcherEnabled = isUserSwitcherEnabled, 682 ) 683 } 684 } 685 686 /** 687 * Maps UserInfo to UserModel based on some parameters and return null under certain conditions 688 * to be filtered out. 689 */ 690 private suspend fun filterAndMapToUserModel( 691 userInfo: UserInfo, 692 selectedUserId: Int, 693 canSwitchUsers: Boolean, 694 isUserSwitcherEnabled: Boolean, 695 ): UserModel? { 696 return when { 697 // When the user switcher is not enabled in settings, we only show the primary user. 698 !isUserSwitcherEnabled && !userInfo.isPrimary -> null 699 // We avoid showing disabled users. 700 !userInfo.isEnabled -> null 701 // We meet the conditions to return the UserModel. 702 userInfo.isGuest || userInfo.supportsSwitchToByUser() -> 703 toUserModel(userInfo, selectedUserId, canSwitchUsers) 704 else -> null 705 } 706 } 707 708 /** Maps UserInfo to UserModel based on some parameters. */ 709 private suspend fun toUserModel( 710 userInfo: UserInfo, 711 selectedUserId: Int, 712 canSwitchUsers: Boolean 713 ): UserModel { 714 val userId = userInfo.id 715 val isSelected = userId == selectedUserId 716 return if (userInfo.isGuest) { 717 UserModel( 718 id = userId, 719 name = Text.Loaded(userInfo.name), 720 image = 721 getUserImage( 722 isGuest = true, 723 userId = userId, 724 ), 725 isSelected = isSelected, 726 isSelectable = canSwitchUsers, 727 isGuest = true, 728 ) 729 } else { 730 UserModel( 731 id = userId, 732 name = Text.Loaded(userInfo.name), 733 image = 734 getUserImage( 735 isGuest = false, 736 userId = userId, 737 ), 738 isSelected = isSelected, 739 isSelectable = canSwitchUsers || isSelected, 740 isGuest = false, 741 ) 742 } 743 } 744 745 private suspend fun canSwitchUsers( 746 selectedUserId: Int, 747 isAction: Boolean = false, 748 ): Boolean { 749 val isHeadlessSystemUserMode = 750 withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() } 751 // Whether menu item should be active. True if item is a user or if any user has 752 // signed in since reboot or in all cases for non-headless system user mode. 753 val isItemEnabled = !isAction || !isHeadlessSystemUserMode || isAnyUserUnlocked() 754 return isItemEnabled && 755 withContext(backgroundDispatcher) { 756 manager.getUserSwitchability(UserHandle.of(selectedUserId)) 757 } == UserManager.SWITCHABILITY_STATUS_OK 758 } 759 760 private suspend fun isAnyUserUnlocked(): Boolean { 761 return manager 762 .getUsers( 763 /* excludePartial= */ true, 764 /* excludeDying= */ true, 765 /* excludePreCreated= */ true 766 ) 767 .any { user -> 768 user.id != UserHandle.USER_SYSTEM && 769 withContext(backgroundDispatcher) { manager.isUserUnlocked(user.userHandle) } 770 } 771 } 772 773 @SuppressLint("UseCompatLoadingForDrawables") 774 private suspend fun getUserImage( 775 isGuest: Boolean, 776 userId: Int, 777 ): Drawable { 778 if (isGuest) { 779 return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle)) 780 } 781 782 // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them. 783 val userIcon = 784 withContext(backgroundDispatcher) { 785 manager.getUserIcon(userId)?.let { bitmap -> 786 val iconSize = 787 applicationContext.resources.getDimensionPixelSize( 788 R.dimen.bouncer_user_switcher_icon_size 789 ) 790 Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize) 791 } 792 } 793 794 if (userIcon != null) { 795 return BitmapDrawable(userIcon) 796 } 797 798 return UserIcons.getDefaultUserIcon( 799 applicationContext.resources, 800 userId, 801 /* light= */ false 802 ) 803 } 804 805 private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean { 806 return guestUserInteractor.isGuestUserAutoCreated || 807 UserActionsUtil.canCreateGuest( 808 manager, 809 repository, 810 settings.isUserSwitcherEnabled, 811 settings.isAddUsersFromLockscreen, 812 ) 813 } 814 815 companion object { 816 private const val TAG = "UserInteractor" 817 } 818 } 819