• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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