Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 247efa91 authored by Anton Potapov's avatar Anton Potapov Committed by Android (Google) Code Review
Browse files

Merge "Migrate from using userId to UserHandle for better type safety" into main

parents c3fa9014 e65fce6d
Loading
Loading
Loading
Loading
+6 −5
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.base.interactor

import android.content.Context
import android.os.UserHandle
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.android.settingslib.RestrictedLockUtils
@@ -32,7 +33,7 @@ import kotlinx.coroutines.withContext

/**
 * Provides restrictions data for the tiles. This is used in
 * [com.android.systemui.qs.tiles.base.viewmodel.BaseQSTileViewModel] to determine if the tile is
 * [com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelImpl] to determine if the tile is
 * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy].
 */
interface DisabledByPolicyInteractor {
@@ -41,7 +42,7 @@ interface DisabledByPolicyInteractor {
     * Checks if the tile is restricted by the policy for a specific user. Pass the result to the
     * [handlePolicyResult] to let the user know that the tile is disable by the admin.
     */
    suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult
    suspend fun isDisabled(user: UserHandle, userRestriction: String?): PolicyResult

    /**
     * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this
@@ -75,14 +76,14 @@ constructor(
    @Background private val backgroundDispatcher: CoroutineDispatcher,
) : DisabledByPolicyInteractor {

    override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult =
    override suspend fun isDisabled(user: UserHandle, userRestriction: String?): PolicyResult =
        withContext(backgroundDispatcher) {
            val admin: EnforcedAdmin =
                restrictedLockProxy.getEnforcedAdmin(userId, userRestriction)
                restrictedLockProxy.getEnforcedAdmin(user.identifier, userRestriction)
                    ?: return@withContext PolicyResult.TileEnabled

            return@withContext if (
                !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction)
                !restrictedLockProxy.hasBaseUserRestriction(user.identifier, userRestriction)
            ) {
                PolicyResult.TileDisabled(admin)
            } else {
+4 −4
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.qs.tiles.base.interactor

import android.os.UserHandle
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow

@@ -29,13 +30,12 @@ interface QSTileDataInteractor<DATA_TYPE> {

    /**
     * Returns a data flow scoped to the user. This means the subscription will live when the tile
     * is listened for the [userId]. It's cancelled when the tile is not listened or the user
     * changes.
     * is listened for the [user]. It's cancelled when the tile is not listened or the user changes.
     *
     * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
     * as possible.
     */
    fun tileData(userId: Int, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>
    fun tileData(user: UserHandle, triggers: Flow<DataUpdateTrigger>): Flow<DATA_TYPE>

    /**
     * Returns tile availability - whether this device currently supports this tile.
@@ -43,5 +43,5 @@ interface QSTileDataInteractor<DATA_TYPE> {
     * You can use [Flow.onStart] on the returned to update the tile with the current state as soon
     * as possible.
     */
    fun availability(userId: Int): Flow<Boolean>
    fun availability(user: UserHandle): Flow<Boolean>
}
+2 −1
Original line number Diff line number Diff line
@@ -16,11 +16,12 @@

package com.android.systemui.qs.tiles.base.interactor

import android.os.UserHandle
import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction

/** @see QSTileUserActionInteractor.handleInput */
data class QSTileInput<T>(
    val userId: Int,
    val user: UserHandle,
    val action: QSTileUserAction,
    val data: T,
)
+13 −13
Original line number Diff line number Diff line
@@ -33,17 +33,17 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher

/**
 * Factory to create an appropriate [BaseQSTileViewModel] instance depending on your circumstances.
 * Factory to create an appropriate [QSTileViewModelImpl] instance depending on your circumstances.
 *
 * @see [QSViewModelFactory.Component]
 * @see [QSViewModelFactory.Static]
 * @see [QSTileViewModelFactory.Component]
 * @see [QSTileViewModelFactory.Static]
 */
sealed interface QSViewModelFactory<T> {
sealed interface QSTileViewModelFactory<T> {

    /**
     * This factory allows you to pass an instance of [QSTileComponent] to a view model effectively
     * binding them together. This achieves a DI scope that lives along the instance of
     * [BaseQSTileViewModel].
     * [QSTileViewModelImpl].
     */
    class Component<T>
    @Inject
@@ -55,14 +55,14 @@ sealed interface QSViewModelFactory<T> {
        private val qsTileLogger: QSTileLogger,
        private val systemClock: SystemClock,
        @Background private val backgroundDispatcher: CoroutineDispatcher,
    ) : QSViewModelFactory<T> {
    ) : QSTileViewModelFactory<T> {

        /**
         * Creates [BaseQSTileViewModel] based on the interactors obtained from [component].
         * Creates [QSTileViewModelImpl] based on the interactors obtained from [component].
         * Reference of that [component] is then stored along the view model.
         */
        fun create(component: QSTileComponent<T>): BaseQSTileViewModel<T> =
            BaseQSTileViewModel(
        fun create(component: QSTileComponent<T>): QSTileViewModelImpl<T> =
            QSTileViewModelImpl(
                component::config,
                component::userActionInteractor,
                component::dataInteractor,
@@ -78,7 +78,7 @@ sealed interface QSViewModelFactory<T> {
    }

    /**
     * This factory passes by necessary implementations to the [BaseQSTileViewModel]. This is a
     * This factory passes by necessary implementations to the [QSTileViewModelImpl]. This is a
     * default choice for most of the tiles.
     */
    class Static<T>
@@ -91,7 +91,7 @@ sealed interface QSViewModelFactory<T> {
        private val qsTileLogger: QSTileLogger,
        private val systemClock: SystemClock,
        @Background private val backgroundDispatcher: CoroutineDispatcher,
    ) : QSViewModelFactory<T> {
    ) : QSTileViewModelFactory<T> {

        /**
         * @param config contains all the static information (like TileSpec) about the tile.
@@ -107,8 +107,8 @@ sealed interface QSViewModelFactory<T> {
            userActionInteractor: QSTileUserActionInteractor<T>,
            tileDataInteractor: QSTileDataInteractor<T>,
            mapper: QSTileDataToStateMapper<T>,
        ): BaseQSTileViewModel<T> =
            BaseQSTileViewModel(
        ): QSTileViewModelImpl<T> =
            QSTileViewModelImpl(
                { config },
                { userActionInteractor },
                { tileDataInteractor },
+17 −20
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@

package com.android.systemui.qs.tiles.base.viewmodel

import androidx.annotation.CallSuper
import android.os.UserHandle
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.tiles.base.analytics.QSTileAnalytics
import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
@@ -62,11 +62,11 @@ import kotlinx.coroutines.flow.stateIn
 * Provides a hassle-free way to implement new tiles according to current System UI architecture
 * standards. This ViewModel is cheap to instantiate and does nothing until its [state] is listened.
 *
 * Don't use this constructor directly. Instead, inject [QSViewModelFactory] to create a new
 * Don't use this constructor directly. Instead, inject [QSTileViewModelFactory] to create a new
 * instance of this class.
 */
@OptIn(ExperimentalCoroutinesApi::class)
class BaseQSTileViewModel<DATA_TYPE>(
class QSTileViewModelImpl<DATA_TYPE>(
    val tileConfig: () -> QSTileConfig,
    private val userActionInteractor: () -> QSTileUserActionInteractor<DATA_TYPE>,
    private val tileDataInteractor: () -> QSTileDataInteractor<DATA_TYPE>,
@@ -81,8 +81,8 @@ class BaseQSTileViewModel<DATA_TYPE>(
    private val tileScope: CoroutineScope = CoroutineScope(SupervisorJob()),
) : QSTileViewModel {

    private val userIds: MutableStateFlow<Int> =
        MutableStateFlow(userRepository.getSelectedUserInfo().id)
    private val users: MutableStateFlow<UserHandle> =
        MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
    private val userInputs: MutableSharedFlow<QSTileUserAction> =
        MutableSharedFlow(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
    private val forceUpdates: MutableSharedFlow<Unit> =
@@ -108,7 +108,7 @@ class BaseQSTileViewModel<DATA_TYPE>(
                replay = 1,
            )
    override val isAvailable: StateFlow<Boolean> =
        userIds
        users
            .flatMapLatest { tileDataInteractor().availability(it) }
            .flowOn(backgroundDispatcher)
            .stateIn(
@@ -117,17 +117,14 @@ class BaseQSTileViewModel<DATA_TYPE>(
                true,
            )

    @CallSuper
    override fun forceUpdate() {
        forceUpdates.tryEmit(Unit)
    }

    @CallSuper
    override fun onUserIdChanged(userId: Int) {
        userIds.tryEmit(userId)
    override fun onUserChanged(user: UserHandle) {
        users.tryEmit(user)
    }

    @CallSuper
    override fun onActionPerformed(userAction: QSTileUserAction) {
        qsTileLogger.logUserAction(
            userAction,
@@ -143,11 +140,11 @@ class BaseQSTileViewModel<DATA_TYPE>(
    }

    private fun createTileDataFlow(): SharedFlow<DATA_TYPE> =
        userIds
            .flatMapLatest { userId ->
        users
            .flatMapLatest { user ->
                val updateTriggers =
                    merge(
                            userInputFlow(userId),
                            userInputFlow(user),
                            forceUpdates
                                .map { DataUpdateTrigger.ForceUpdate }
                                .onEach { qsTileLogger.logForceUpdate(spec) },
@@ -157,7 +154,7 @@ class BaseQSTileViewModel<DATA_TYPE>(
                            qsTileLogger.logInitialRequest(spec)
                        }
                tileDataInteractor()
                    .tileData(userId, updateTriggers)
                    .tileData(user, updateTriggers)
                    .cancellable()
                    .flowOn(backgroundDispatcher)
            }
@@ -176,10 +173,10 @@ class BaseQSTileViewModel<DATA_TYPE>(
     *
     * Subscribing to the result flow twice will result in doubling all actions, logs and analytics.
     */
    private fun userInputFlow(userId: Int): Flow<DataUpdateTrigger> {
    private fun userInputFlow(user: UserHandle): Flow<DataUpdateTrigger> {
        return userInputs
            .filterFalseActions()
            .filterByPolicy(userId)
            .filterByPolicy(user)
            .throttle(CLICK_THROTTLE_DURATION, systemClock)
            // Skip the input until there is some data
            .mapNotNull { action ->
@@ -188,20 +185,20 @@ class BaseQSTileViewModel<DATA_TYPE>(
                qsTileLogger.logUserActionPipeline(spec, action, state, data)
                qsTileAnalytics.trackUserAction(config, action)

                DataUpdateTrigger.UserInput(QSTileInput(userId, action, data))
                DataUpdateTrigger.UserInput(QSTileInput(user, action, data))
            }
            .onEach { userActionInteractor().handleInput(it.input) }
            .flowOn(backgroundDispatcher)
    }

    private fun Flow<QSTileUserAction>.filterByPolicy(userId: Int): Flow<QSTileUserAction> =
    private fun Flow<QSTileUserAction>.filterByPolicy(user: UserHandle): Flow<QSTileUserAction> =
        config.policy.let { policy ->
            when (policy) {
                is QSTilePolicy.NoRestrictions -> this@filterByPolicy
                is QSTilePolicy.Restricted ->
                    filter { action ->
                        val result =
                            disabledByPolicyInteractor.isDisabled(userId, policy.userRestriction)
                            disabledByPolicyInteractor.isDisabled(user, policy.userRestriction)
                        !disabledByPolicyInteractor.handlePolicyResult(result).also { isDisabled ->
                            if (isDisabled) {
                                qsTileLogger.logUserActionRejectedByPolicy(action, spec)
Loading