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