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