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 package com.android.systemui.user.data.repository
18 
19 import android.content.Context
20 import android.graphics.drawable.Drawable
21 import android.os.Handler
22 import android.os.UserManager
23 import android.provider.Settings.Global.USER_SWITCHER_ENABLED
24 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
25 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
26 import com.android.systemui.dagger.SysUISingleton
27 import com.android.systemui.dagger.qualifiers.Application
28 import com.android.systemui.dagger.qualifiers.Background
29 import com.android.systemui.qs.SettingObserver
30 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
31 import com.android.systemui.res.R
32 import com.android.systemui.statusbar.policy.UserInfoController
33 import com.android.systemui.statusbar.policy.UserSwitcherController
34 import com.android.systemui.util.settings.GlobalSettings
35 import javax.inject.Inject
36 import kotlinx.coroutines.CoroutineDispatcher
37 import kotlinx.coroutines.channels.awaitClose
38 import kotlinx.coroutines.flow.Flow
39 import kotlinx.coroutines.flow.combine
40 import kotlinx.coroutines.flow.distinctUntilChanged
41 import kotlinx.coroutines.flow.flatMapLatest
42 import kotlinx.coroutines.flow.flowOf
43 import kotlinx.coroutines.launch
44 import kotlinx.coroutines.withContext
45 
46 interface UserSwitcherRepository {
47     /** The current [UserSwitcherStatusModel]. */
48     val userSwitcherStatus: Flow<UserSwitcherStatusModel>
49 
50     /** Whether the user switcher is currently enabled. */
51     val isEnabled: Flow<Boolean>
52 }
53 
54 @SysUISingleton
55 class UserSwitcherRepositoryImpl
56 @Inject
57 constructor(
58     @Application private val context: Context,
59     @Background private val bgHandler: Handler,
60     @Background private val bgDispatcher: CoroutineDispatcher,
61     private val userManager: UserManager,
62     private val userSwitcherController: UserSwitcherController,
63     private val userInfoController: UserInfoController,
64     private val globalSetting: GlobalSettings,
65     private val userRepository: UserRepository,
66 ) : UserSwitcherRepository {
67     private val showUserSwitcherForSingleUser =
68         context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
69 
<lambda>null70     override val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
71         suspend fun updateState() {
72             trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
73         }
74 
75         val observer =
76             object :
77                 SettingObserver(
78                     globalSetting,
79                     bgHandler,
80                     USER_SWITCHER_ENABLED,
81                     userRepository.getSelectedUserInfo().id,
82                 ) {
83                 override fun handleValueChanged(value: Int, observedChange: Boolean) {
84                     if (observedChange) {
85                         launch { updateState() }
86                     }
87                 }
88             }
89 
90         observer.isListening = true
91         updateState()
92         awaitClose { observer.isListening = false }
93     }
94 
95     /** The current user name. */
<lambda>null96     private val currentUserName: Flow<String?> = conflatedCallbackFlow {
97         suspend fun updateState() {
98             trySendWithFailureLogging(getCurrentUser(), TAG)
99         }
100 
101         val callback = UserSwitcherController.UserSwitchCallback { launch { updateState() } }
102 
103         userSwitcherController.addUserSwitchCallback(callback)
104         updateState()
105         awaitClose { userSwitcherController.removeUserSwitchCallback(callback) }
106     }
107 
108     /** The current (icon, isGuestUser) values. */
109     // TODO(b/242040009): Could we only use this callback to get the user name and remove
110     // currentUsername above?
<lambda>null111     private val currentUserInfo: Flow<Pair<Drawable?, Boolean>> = conflatedCallbackFlow {
112         val listener =
113             UserInfoController.OnUserInfoChangedListener { _, picture, _ ->
114                 launch { trySendWithFailureLogging(picture to isGuestUser(), TAG) }
115             }
116 
117         // This will automatically call the listener when attached, so no need to update the state
118         // here.
119         userInfoController.addCallback(listener)
120         awaitClose { userInfoController.removeCallback(listener) }
121     }
122 
123     override val userSwitcherStatus: Flow<UserSwitcherStatusModel> =
124         isEnabled
enablednull125             .flatMapLatest { enabled ->
126                 if (enabled) {
127                     combine(currentUserName, currentUserInfo) { name, (icon, isGuest) ->
128                         UserSwitcherStatusModel.Enabled(name, icon, isGuest)
129                     }
130                 } else {
131                     flowOf(UserSwitcherStatusModel.Disabled)
132                 }
133             }
134             .distinctUntilChanged()
135 
isUserSwitcherEnablednull136     private suspend fun isUserSwitcherEnabled(): Boolean {
137         return withContext(bgDispatcher) {
138             userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
139         }
140     }
141 
getCurrentUsernull142     private suspend fun getCurrentUser(): String? {
143         return withContext(bgDispatcher) { userSwitcherController.currentUserName }
144     }
145 
isGuestUsernull146     private suspend fun isGuestUser(): Boolean {
147         return withContext(bgDispatcher) {
148             userManager.isGuestUser(userRepository.getSelectedUserInfo().id)
149         }
150     }
151 
152     companion object {
153         private const val TAG = "UserSwitcherRepositoryImpl"
154     }
155 }
156