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