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