Loading packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt +6 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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 Loading Loading @@ -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 { Loading packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt +4 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading @@ -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> } packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt +2 −1 Original line number Diff line number Diff line Loading @@ -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, ) packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSViewModelFactory.kt→packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +13 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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> Loading @@ -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. Loading @@ -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 }, Loading packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt→packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +17 −20 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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>, Loading @@ -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> = Loading @@ -108,7 +108,7 @@ class BaseQSTileViewModel<DATA_TYPE>( replay = 1, ) override val isAvailable: StateFlow<Boolean> = userIds users .flatMapLatest { tileDataInteractor().availability(it) } .flowOn(backgroundDispatcher) .stateIn( Loading @@ -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, Loading @@ -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) }, Loading @@ -157,7 +154,7 @@ class BaseQSTileViewModel<DATA_TYPE>( qsTileLogger.logInitialRequest(spec) } tileDataInteractor() .tileData(userId, updateTriggers) .tileData(user, updateTriggers) .cancellable() .flowOn(backgroundDispatcher) } Loading @@ -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 -> Loading @@ -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 Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt +6 −5 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 { Loading @@ -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 Loading Loading @@ -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 { Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt +4 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading @@ -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> }
packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileInput.kt +2 −1 Original line number Diff line number Diff line Loading @@ -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, )
packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSViewModelFactory.kt→packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt +13 −13 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading @@ -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> Loading @@ -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. Loading @@ -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 }, Loading
packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt→packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImpl.kt +17 −20 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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>, Loading @@ -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> = Loading @@ -108,7 +108,7 @@ class BaseQSTileViewModel<DATA_TYPE>( replay = 1, ) override val isAvailable: StateFlow<Boolean> = userIds users .flatMapLatest { tileDataInteractor().availability(it) } .flowOn(backgroundDispatcher) .stateIn( Loading @@ -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, Loading @@ -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) }, Loading @@ -157,7 +154,7 @@ class BaseQSTileViewModel<DATA_TYPE>( qsTileLogger.logInitialRequest(spec) } tileDataInteractor() .tileData(userId, updateTriggers) .tileData(user, updateTriggers) .cancellable() .flowOn(backgroundDispatcher) } Loading @@ -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 -> Loading @@ -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