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