• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.data.repository
19 
20 import android.content.Context
21 import android.content.pm.UserInfo
22 import android.os.UserHandle
23 import android.os.UserManager
24 import android.provider.Settings
25 import androidx.annotation.VisibleForTesting
26 import com.android.systemui.R
27 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
28 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
29 import com.android.systemui.dagger.SysUISingleton
30 import com.android.systemui.dagger.qualifiers.Application
31 import com.android.systemui.dagger.qualifiers.Background
32 import com.android.systemui.dagger.qualifiers.Main
33 import com.android.systemui.flags.FeatureFlags
34 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
35 import com.android.systemui.settings.UserTracker
36 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
37 import com.android.systemui.util.settings.GlobalSettings
38 import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
39 import java.util.concurrent.atomic.AtomicBoolean
40 import javax.inject.Inject
41 import kotlinx.coroutines.CoroutineDispatcher
42 import kotlinx.coroutines.CoroutineScope
43 import kotlinx.coroutines.asExecutor
44 import kotlinx.coroutines.channels.awaitClose
45 import kotlinx.coroutines.flow.Flow
46 import kotlinx.coroutines.flow.MutableStateFlow
47 import kotlinx.coroutines.flow.SharingStarted
48 import kotlinx.coroutines.flow.StateFlow
49 import kotlinx.coroutines.flow.filterNotNull
50 import kotlinx.coroutines.flow.launchIn
51 import kotlinx.coroutines.flow.map
52 import kotlinx.coroutines.flow.onEach
53 import kotlinx.coroutines.flow.onStart
54 import kotlinx.coroutines.flow.stateIn
55 import kotlinx.coroutines.launch
56 import kotlinx.coroutines.runBlocking
57 import kotlinx.coroutines.withContext
58 
59 /**
60  * Acts as source of truth for user related data.
61  *
62  * Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
63  * upstream changes.
64  */
65 interface UserRepository {
66     /** User switcher related settings. */
67     val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
68 
69     /** List of all users on the device. */
70     val userInfos: Flow<List<UserInfo>>
71 
72     /** [UserInfo] of the currently-selected user. */
73     val selectedUserInfo: Flow<UserInfo>
74 
75     /** Whether user switching is currently in progress. */
76     val userSwitchingInProgress: Flow<Boolean>
77 
78     /** User ID of the last non-guest selected user. */
79     val lastSelectedNonGuestUserId: Int
80 
81     /** Whether the device is configured to always have a guest user available. */
82     val isGuestUserAutoCreated: Boolean
83 
84     /** Whether the guest user is currently being reset. */
85     var isGuestUserResetting: Boolean
86 
87     /** Whether we've scheduled the creation of a guest user. */
88     val isGuestUserCreationScheduled: AtomicBoolean
89 
90     /** Whether to enable the status bar user chip (which launches the user switcher) */
91     val isStatusBarUserChipEnabled: Boolean
92 
93     /** The user of the secondary service. */
94     var secondaryUserId: Int
95 
96     /** Whether refresh users should be paused. */
97     var isRefreshUsersPaused: Boolean
98 
99     /** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
refreshUsersnull100     fun refreshUsers()
101 
102     fun getSelectedUserInfo(): UserInfo
103 
104     fun isSimpleUserSwitcher(): Boolean
105 }
106 
107 @SysUISingleton
108 class UserRepositoryImpl
109 @Inject
110 constructor(
111     @Application private val appContext: Context,
112     private val manager: UserManager,
113     @Application private val applicationScope: CoroutineScope,
114     @Main private val mainDispatcher: CoroutineDispatcher,
115     @Background private val backgroundDispatcher: CoroutineDispatcher,
116     private val globalSettings: GlobalSettings,
117     private val tracker: UserTracker,
118     featureFlags: FeatureFlags,
119 ) : UserRepository {
120 
121     private val _userSwitcherSettings: StateFlow<UserSwitcherSettingsModel> =
122         globalSettings
123             .observerFlow(
124                 names =
125                     arrayOf(
126                         SETTING_SIMPLE_USER_SWITCHER,
127                         Settings.Global.ADD_USERS_WHEN_LOCKED,
128                         Settings.Global.USER_SWITCHER_ENABLED,
129                     ),
130                 userId = UserHandle.USER_SYSTEM,
131             )
132             .onStart { emit(Unit) } // Forces an initial update.
133             .map { getSettings() }
134             .stateIn(
135                 scope = applicationScope,
136                 started = SharingStarted.Eagerly,
137                 initialValue = runBlocking { getSettings() },
138             )
139     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> = _userSwitcherSettings
140 
141     private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
142     override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
143 
144     private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
145     override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
146 
147     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
148         private set
149 
150     override val isGuestUserAutoCreated: Boolean =
151         appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
152 
153     private var _isGuestUserResetting: Boolean = false
154     override var isGuestUserResetting: Boolean = _isGuestUserResetting
155 
156     private val _isUserSwitchingInProgress = MutableStateFlow(false)
157     override val userSwitchingInProgress: Flow<Boolean>
158         get() = _isUserSwitchingInProgress
159 
160     override val isGuestUserCreationScheduled = AtomicBoolean()
161 
162     override val isStatusBarUserChipEnabled: Boolean =
163         appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
164 
165     override var secondaryUserId: Int = UserHandle.USER_NULL
166 
167     override var isRefreshUsersPaused: Boolean = false
168 
169     init {
170         observeSelectedUser()
171         if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
172             observeUserSwitching()
173         }
174     }
175 
176     override fun refreshUsers() {
177         applicationScope.launch {
178             val result = withContext(backgroundDispatcher) { manager.aliveUsers }
179 
180             if (result != null) {
181                 _userInfos.value =
182                     result
183                         // Users should be sorted by ascending creation time.
184                         .sortedBy { it.creationTime }
185                         // The guest user is always last, regardless of creation time.
186                         .sortedBy { it.isGuest }
187             }
188         }
189     }
190 
191     override fun getSelectedUserInfo(): UserInfo {
192         return checkNotNull(_selectedUserInfo.value)
193     }
194 
195     override fun isSimpleUserSwitcher(): Boolean {
196         return _userSwitcherSettings.value.isSimpleUserSwitcher
197     }
198 
199     private fun observeUserSwitching() {
200         conflatedCallbackFlow {
201                 val callback =
202                     object : UserTracker.Callback {
203                         override fun onUserChanging(newUser: Int, userContext: Context) {
204                             trySendWithFailureLogging(true, TAG, "userSwitching started")
205                         }
206 
207                         override fun onUserChanged(newUserId: Int, userContext: Context) {
208                             trySendWithFailureLogging(false, TAG, "userSwitching completed")
209                         }
210                     }
211                 tracker.addCallback(callback, mainDispatcher.asExecutor())
212                 trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
213                 awaitClose { tracker.removeCallback(callback) }
214             }
215             .onEach { _isUserSwitchingInProgress.value = it }
216             // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
217             //  once the flag is launched
218             .launchIn(applicationScope)
219     }
220 
221     private fun observeSelectedUser() {
222         conflatedCallbackFlow {
223                 fun send() {
224                     trySendWithFailureLogging(tracker.userInfo, TAG)
225                 }
226 
227                 val callback =
228                     object : UserTracker.Callback {
229                         override fun onUserChanging(newUser: Int, userContext: Context) {
230                             send()
231                         }
232 
233                         override fun onProfilesChanged(profiles: List<UserInfo>) {
234                             send()
235                         }
236                     }
237 
238                 tracker.addCallback(callback, mainDispatcher.asExecutor())
239                 send()
240 
241                 awaitClose { tracker.removeCallback(callback) }
242             }
243             .onEach {
244                 if (!it.isGuest) {
245                     lastSelectedNonGuestUserId = it.id
246                 }
247 
248                 _selectedUserInfo.value = it
249             }
250             .launchIn(applicationScope)
251     }
252 
253     private suspend fun getSettings(): UserSwitcherSettingsModel {
254         return withContext(backgroundDispatcher) {
255             val isSimpleUserSwitcher =
256                 globalSettings.getIntForUser(
257                     SETTING_SIMPLE_USER_SWITCHER,
258                     if (
259                         appContext.resources.getBoolean(
260                             com.android.internal.R.bool.config_expandLockScreenUserSwitcher
261                         )
262                     ) {
263                         1
264                     } else {
265                         0
266                     },
267                     UserHandle.USER_SYSTEM,
268                 ) != 0
269 
270             val isAddUsersFromLockscreen =
271                 globalSettings.getIntForUser(
272                     Settings.Global.ADD_USERS_WHEN_LOCKED,
273                     0,
274                     UserHandle.USER_SYSTEM,
275                 ) != 0
276 
277             val isUserSwitcherEnabled =
278                 globalSettings.getIntForUser(
279                     Settings.Global.USER_SWITCHER_ENABLED,
280                     if (
281                         appContext.resources.getBoolean(
282                             com.android.internal.R.bool.config_showUserSwitcherByDefault
283                         )
284                     ) {
285                         1
286                     } else {
287                         0
288                     },
289                     UserHandle.USER_SYSTEM,
290                 ) != 0
291 
292             UserSwitcherSettingsModel(
293                 isSimpleUserSwitcher = isSimpleUserSwitcher,
294                 isAddUsersFromLockscreen = isAddUsersFromLockscreen,
295                 isUserSwitcherEnabled = isUserSwitcherEnabled,
296             )
297         }
298     }
299 
300     companion object {
301         private const val TAG = "UserRepository"
302         @VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
303     }
304 }
305