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 package com.android.systemui.qs.footer.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.keyguard.KeyguardUpdateMonitor
25 import com.android.systemui.R
26 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
27 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
28 import com.android.systemui.dagger.SysUISingleton
29 import com.android.systemui.dagger.qualifiers.Application
30 import com.android.systemui.dagger.qualifiers.Background
31 import com.android.systemui.qs.SettingObserver
32 import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
33 import com.android.systemui.settings.UserTracker
34 import com.android.systemui.statusbar.policy.UserInfoController
35 import com.android.systemui.statusbar.policy.UserSwitcherController
36 import com.android.systemui.util.settings.GlobalSettings
37 import javax.inject.Inject
38 import kotlinx.coroutines.CoroutineDispatcher
39 import kotlinx.coroutines.channels.awaitClose
40 import kotlinx.coroutines.flow.Flow
41 import kotlinx.coroutines.flow.combine
42 import kotlinx.coroutines.flow.distinctUntilChanged
43 import kotlinx.coroutines.flow.flatMapLatest
44 import kotlinx.coroutines.flow.flowOf
45 import kotlinx.coroutines.launch
46 import kotlinx.coroutines.withContext
47 
48 interface UserSwitcherRepository {
49     /** The current [UserSwitcherStatusModel]. */
50     val userSwitcherStatus: Flow<UserSwitcherStatusModel>
51 }
52 
53 @SysUISingleton
54 class UserSwitcherRepositoryImpl
55 @Inject
56 constructor(
57     @Application private val context: Context,
58     @Background private val bgHandler: Handler,
59     @Background private val bgDispatcher: CoroutineDispatcher,
60     private val userManager: UserManager,
61     private val userTracker: UserTracker,
62     private val userSwitcherController: UserSwitcherController,
63     private val userInfoController: UserInfoController,
64     private val globalSetting: GlobalSettings,
65 ) : UserSwitcherRepository {
66     private val showUserSwitcherForSingleUser =
67         context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
68 
69     /** Whether the user switcher is currently enabled. */
70     private 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                     userTracker.userId,
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. */
96     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?
111     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
125             .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 
136     private suspend fun isUserSwitcherEnabled(): Boolean {
137         return withContext(bgDispatcher) {
138             userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
139         }
140     }
141 
142     private suspend fun getCurrentUser(): String? {
143         return withContext(bgDispatcher) { userSwitcherController.currentUserName }
144     }
145 
146     private suspend fun isGuestUser(): Boolean {
147         return withContext(bgDispatcher) {
148             userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
149         }
150     }
151 
152     companion object {
153         private const val TAG = "UserSwitcherRepositoryImpl"
154     }
155 }
156