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