• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * 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         val intent = Intent(applicationContext, SystemUISecondaryUserService::class.java)
645         // Disconnect from the old secondary user's service
646         val secondaryUserId = repository.secondaryUserId
647         if (secondaryUserId != UserHandle.USER_NULL) {
648             applicationContext.stopServiceAsUser(
649                 intent,
650                 UserHandle.of(secondaryUserId),
651             )
652             repository.secondaryUserId = UserHandle.USER_NULL
653         }
654 
655         // Connect to the new secondary user's service (purely to ensure that a persistent
656         // SystemUI application is created for that user)
657         if (userId != Process.myUserHandle().identifier) {
658             applicationContext.startServiceAsUser(
659                 intent,
660                 UserHandle.of(userId),
661             )
662             repository.secondaryUserId = userId
663         }
664     }
665 
666     private suspend fun toUserModels(
667         userInfos: List<UserInfo>,
668         selectedUserId: Int,
669         isUserSwitcherEnabled: Boolean,
670     ): List<UserModel> {
671         val canSwitchUsers = canSwitchUsers(selectedUserId)
672 
673         return userInfos
674             // The guest user should go in the last position.
675             .sortedBy { it.isGuest }
676             .mapNotNull { userInfo ->
677                 filterAndMapToUserModel(
678                     userInfo = userInfo,
679                     selectedUserId = selectedUserId,
680                     canSwitchUsers = canSwitchUsers,
681                     isUserSwitcherEnabled = isUserSwitcherEnabled,
682                 )
683             }
684     }
685 
686     /**
687      * Maps UserInfo to UserModel based on some parameters and return null under certain conditions
688      * to be filtered out.
689      */
690     private suspend fun filterAndMapToUserModel(
691         userInfo: UserInfo,
692         selectedUserId: Int,
693         canSwitchUsers: Boolean,
694         isUserSwitcherEnabled: Boolean,
695     ): UserModel? {
696         return when {
697             // When the user switcher is not enabled in settings, we only show the primary user.
698             !isUserSwitcherEnabled && !userInfo.isPrimary -> null
699             // We avoid showing disabled users.
700             !userInfo.isEnabled -> null
701             // We meet the conditions to return the UserModel.
702             userInfo.isGuest || userInfo.supportsSwitchToByUser() ->
703                 toUserModel(userInfo, selectedUserId, canSwitchUsers)
704             else -> null
705         }
706     }
707 
708     /** Maps UserInfo to UserModel based on some parameters. */
709     private suspend fun toUserModel(
710         userInfo: UserInfo,
711         selectedUserId: Int,
712         canSwitchUsers: Boolean
713     ): UserModel {
714         val userId = userInfo.id
715         val isSelected = userId == selectedUserId
716         return if (userInfo.isGuest) {
717             UserModel(
718                 id = userId,
719                 name = Text.Loaded(userInfo.name),
720                 image =
721                     getUserImage(
722                         isGuest = true,
723                         userId = userId,
724                     ),
725                 isSelected = isSelected,
726                 isSelectable = canSwitchUsers,
727                 isGuest = true,
728             )
729         } else {
730             UserModel(
731                 id = userId,
732                 name = Text.Loaded(userInfo.name),
733                 image =
734                     getUserImage(
735                         isGuest = false,
736                         userId = userId,
737                     ),
738                 isSelected = isSelected,
739                 isSelectable = canSwitchUsers || isSelected,
740                 isGuest = false,
741             )
742         }
743     }
744 
745     private suspend fun canSwitchUsers(
746         selectedUserId: Int,
747         isAction: Boolean = false,
748     ): 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(
775         isGuest: Boolean,
776         userId: Int,
777     ): Drawable {
778         if (isGuest) {
779             return checkNotNull(applicationContext.getDrawable(R.drawable.ic_account_circle))
780         }
781 
782         // TODO(b/246631653): cache the bitmaps to avoid the background work to fetch them.
783         val userIcon =
784             withContext(backgroundDispatcher) {
785                 manager.getUserIcon(userId)?.let { bitmap ->
786                     val iconSize =
787                         applicationContext.resources.getDimensionPixelSize(
788                             R.dimen.bouncer_user_switcher_icon_size
789                         )
790                     Icon.scaleDownIfNecessary(bitmap, iconSize, iconSize)
791                 }
792             }
793 
794         if (userIcon != null) {
795             return BitmapDrawable(userIcon)
796         }
797 
798         return UserIcons.getDefaultUserIcon(
799             applicationContext.resources,
800             userId,
801             /* light= */ false
802         )
803     }
804 
805     private fun canCreateGuestUser(settings: UserSwitcherSettingsModel): Boolean {
806         return guestUserInteractor.isGuestUserAutoCreated ||
807             UserActionsUtil.canCreateGuest(
808                 manager,
809                 repository,
810                 settings.isUserSwitcherEnabled,
811                 settings.isAddUsersFromLockscreen,
812             )
813     }
814 
815     companion object {
816         private const val TAG = "UserInteractor"
817     }
818 }
819