blob: e5ab47325229d2388253827368b1d8304ac89b02 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.user.data.repository
import android.app.IActivityManager
import android.app.UserSwitchObserver
import android.content.Context
import android.content.pm.UserInfo
import android.os.IRemoteCallback
import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/**
* Acts as source of truth for user related data.
*
* Abstracts-away data sources and their schemas so the rest of the app doesn't need to worry about
* upstream changes.
*/
interface UserRepository {
/** User switcher related settings. */
val userSwitcherSettings: Flow<UserSwitcherSettingsModel>
/** List of all users on the device. */
val userInfos: Flow<List<UserInfo>>
/** [UserInfo] of the currently-selected user. */
val selectedUserInfo: Flow<UserInfo>
/** Whether user switching is currently in progress. */
val userSwitchingInProgress: Flow<Boolean>
/** User ID of the last non-guest selected user. */
val lastSelectedNonGuestUserId: Int
/** Whether the device is configured to always have a guest user available. */
val isGuestUserAutoCreated: Boolean
/** Whether the guest user is currently being reset. */
var isGuestUserResetting: Boolean
/** Whether we've scheduled the creation of a guest user. */
val isGuestUserCreationScheduled: AtomicBoolean
/** Whether to enable the status bar user chip (which launches the user switcher) */
val isStatusBarUserChipEnabled: Boolean
/** The user of the secondary service. */
var secondaryUserId: Int
/** Whether refresh users should be paused. */
var isRefreshUsersPaused: Boolean
/** Asynchronously refresh the list of users. This will cause [userInfos] to be updated. */
fun refreshUsers()
fun getSelectedUserInfo(): UserInfo
fun isSimpleUserSwitcher(): Boolean
}
@SysUISingleton
class UserRepositoryImpl
@Inject
constructor(
@Application private val appContext: Context,
private val manager: UserManager,
@Application private val applicationScope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
@Background private val backgroundDispatcher: CoroutineDispatcher,
private val globalSettings: GlobalSettings,
private val tracker: UserTracker,
private val activityManager: IActivityManager,
featureFlags: FeatureFlags,
) : UserRepository {
private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
_userSwitcherSettings.asStateFlow().filterNotNull()
private val _userInfos = MutableStateFlow<List<UserInfo>?>(null)
override val userInfos: Flow<List<UserInfo>> = _userInfos.filterNotNull()
private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
private set
override val isGuestUserAutoCreated: Boolean =
appContext.resources.getBoolean(com.android.internal.R.bool.config_guestUserAutoCreated)
private var _isGuestUserResetting: Boolean = false
override var isGuestUserResetting: Boolean = _isGuestUserResetting
private val _isUserSwitchingInProgress = MutableStateFlow(false)
override val userSwitchingInProgress: Flow<Boolean>
get() = _isUserSwitchingInProgress
override val isGuestUserCreationScheduled = AtomicBoolean()
override val isStatusBarUserChipEnabled: Boolean =
appContext.resources.getBoolean(R.bool.flag_user_switcher_chip)
override var secondaryUserId: Int = UserHandle.USER_NULL
override var isRefreshUsersPaused: Boolean = false
init {
observeSelectedUser()
observeUserSettings()
if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
observeUserSwitching()
}
}
override fun refreshUsers() {
applicationScope.launch {
val result = withContext(backgroundDispatcher) { manager.aliveUsers }
if (result != null) {
_userInfos.value =
result
// Users should be sorted by ascending creation time.
.sortedBy { it.creationTime }
// The guest user is always last, regardless of creation time.
.sortedBy { it.isGuest }
}
}
}
override fun getSelectedUserInfo(): UserInfo {
return checkNotNull(_selectedUserInfo.value)
}
override fun isSimpleUserSwitcher(): Boolean {
return _userSwitcherSettings.value.isSimpleUserSwitcher
}
private fun observeUserSwitching() {
conflatedCallbackFlow {
val callback =
object : UserSwitchObserver() {
override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
trySendWithFailureLogging(true, TAG, "userSwitching started")
}
override fun onUserSwitchComplete(newUserId: Int) {
trySendWithFailureLogging(false, TAG, "userSwitching completed")
}
}
activityManager.registerUserSwitchObserver(callback, TAG)
trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
}
.onEach { _isUserSwitchingInProgress.value = it }
// TODO (b/262838215), Make this stateIn and initialize directly in field declaration
// once the flag is launched
.launchIn(applicationScope)
}
private fun observeSelectedUser() {
conflatedCallbackFlow {
fun send() {
trySendWithFailureLogging(tracker.userInfo, TAG)
}
val callback =
object : UserTracker.Callback {
override fun onUserChanging(newUser: Int, userContext: Context) {
send()
}
override fun onProfilesChanged(profiles: List<UserInfo>) {
send()
}
}
tracker.addCallback(callback, mainDispatcher.asExecutor())
send()
awaitClose { tracker.removeCallback(callback) }
}
.onEach {
if (!it.isGuest) {
lastSelectedNonGuestUserId = it.id
}
_selectedUserInfo.value = it
}
.launchIn(applicationScope)
}
private fun observeUserSettings() {
globalSettings
.observerFlow(
names =
arrayOf(
SETTING_SIMPLE_USER_SWITCHER,
Settings.Global.ADD_USERS_WHEN_LOCKED,
Settings.Global.USER_SWITCHER_ENABLED,
),
userId = UserHandle.USER_SYSTEM,
)
.onStart { emit(Unit) } // Forces an initial update.
.map { getSettings() }
.onEach { _userSwitcherSettings.value = it }
.launchIn(applicationScope)
}
private suspend fun getSettings(): UserSwitcherSettingsModel {
return withContext(backgroundDispatcher) {
val isSimpleUserSwitcher =
globalSettings.getIntForUser(
SETTING_SIMPLE_USER_SWITCHER,
if (
appContext.resources.getBoolean(
com.android.internal.R.bool.config_expandLockScreenUserSwitcher
)
) {
1
} else {
0
},
UserHandle.USER_SYSTEM,
) != 0
val isAddUsersFromLockscreen =
globalSettings.getIntForUser(
Settings.Global.ADD_USERS_WHEN_LOCKED,
0,
UserHandle.USER_SYSTEM,
) != 0
val isUserSwitcherEnabled =
globalSettings.getIntForUser(
Settings.Global.USER_SWITCHER_ENABLED,
if (
appContext.resources.getBoolean(
com.android.internal.R.bool.config_showUserSwitcherByDefault
)
) {
1
} else {
0
},
UserHandle.USER_SYSTEM,
) != 0
UserSwitcherSettingsModel(
isSimpleUserSwitcher = isSimpleUserSwitcher,
isAddUsersFromLockscreen = isAddUsersFromLockscreen,
isUserSwitcherEnabled = isUserSwitcherEnabled,
)
}
}
companion object {
private const val TAG = "UserRepository"
@VisibleForTesting const val SETTING_SIMPLE_USER_SWITCHER = "lockscreenSimpleUserSwitcher"
}
}