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