diff options
23 files changed, 669 insertions, 41 deletions
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 104f3d2b2621..ee05f2d9101b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -363,6 +363,8 @@ filegroup { "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt", "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt", "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt", + "tests/src/com/android/systemui/qs/tiles/base/**/*.kt", + "tests/src/com/android/systemui/qs/tiles/viewmodel/**/*.kt", ], path: "tests/src", } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index 21aaa94f9e10..b50798e59953 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -32,6 +32,8 @@ import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractorImpl import com.android.systemui.qs.pipeline.domain.startable.QSPipelineCoreStartable import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractorImpl import dagger.Binds import dagger.Module import dagger.Provides @@ -61,6 +63,11 @@ abstract class QSPipelineModule { ): InstalledTilesComponentRepository @Binds + abstract fun provideDisabledByPolicyInteractor( + impl: DisabledByPolicyInteractorImpl + ): DisabledByPolicyInteractor + + @Binds @IntoMap @ClassKey(QSPipelineCoreStartable::class) abstract fun provideCoreStartable(startable: QSPipelineCoreStartable): CoreStartable diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt index dc9e11567676..9d100728e643 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandler.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.actions import android.content.Intent diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt new file mode 100644 index 000000000000..056f967b09bb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractor.kt @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor + +import android.content.Context +import androidx.annotation.VisibleForTesting +import androidx.annotation.WorkerThread +import com.android.settingslib.RestrictedLockUtils +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.settingslib.RestrictedLockUtilsInternal +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor.PolicyResult +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +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 + * disabled based on the [com.android.systemui.qs.tiles.viewmodel.QSTileConfig.policy]. + */ +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 + + /** + * Returns true when [policyResult] is [PolicyResult.TileDisabled] and has been handled by this + * method. No further handling is required and the input event can be skipped at this point. + * + * Returns false when [policyResult] is [PolicyResult.TileEnabled] and this method has done + * nothing. + */ + fun handlePolicyResult(policyResult: PolicyResult): Boolean + + sealed interface PolicyResult { + /** Tile has no policy restrictions. */ + data object TileEnabled : PolicyResult + + /** + * Tile is disabled by policy. Pass this to [DisabledByPolicyInteractor.handlePolicyResult] + * to show the user info screen using + * [RestrictedLockUtils.getShowAdminSupportDetailsIntent]. + */ + data class TileDisabled(val admin: EnforcedAdmin) : PolicyResult + } +} + +@SysUISingleton +class DisabledByPolicyInteractorImpl +@Inject +constructor( + private val context: Context, + private val activityStarter: ActivityStarter, + private val restrictedLockProxy: RestrictedLockProxy, + @Background private val backgroundDispatcher: CoroutineDispatcher, +) : DisabledByPolicyInteractor { + + override suspend fun isDisabled(userId: Int, userRestriction: String?): PolicyResult = + withContext(backgroundDispatcher) { + val admin: EnforcedAdmin = + restrictedLockProxy.getEnforcedAdmin(userId, userRestriction) + ?: return@withContext PolicyResult.TileEnabled + + return@withContext if ( + !restrictedLockProxy.hasBaseUserRestriction(userId, userRestriction) + ) { + PolicyResult.TileDisabled(admin) + } else { + PolicyResult.TileEnabled + } + } + + override fun handlePolicyResult(policyResult: PolicyResult): Boolean = + when (policyResult) { + is PolicyResult.TileEnabled -> false + is PolicyResult.TileDisabled -> { + val intent = + RestrictedLockUtils.getShowAdminSupportDetailsIntent( + context, + policyResult.admin + ) + activityStarter.postStartActivityDismissingKeyguard(intent, 0) + true + } + } +} + +/** Mockable proxy for [RestrictedLockUtilsInternal] static methods. */ +@VisibleForTesting +class RestrictedLockProxy @Inject constructor(private val context: Context) { + + @WorkerThread + fun hasBaseUserRestriction(userId: Int, userRestriction: String?): Boolean = + RestrictedLockUtilsInternal.hasBaseUserRestriction( + context, + userRestriction, + userId, + ) + + @WorkerThread + fun getEnforcedAdmin(userId: Int, userRestriction: String?): EnforcedAdmin? = + RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + context, + userRestriction, + userId, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt index 1a03481b9fca..7a22e3cf8bc8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor import com.android.systemui.qs.tiles.viewmodel.QSTileState diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt index 82897044f06c..0aa6b0be5485 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataRequest.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor data class QSTileDataRequest( diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt index d6c9705a6aa1..2bc664311644 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileDataToStateMapper.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor import androidx.annotation.WorkerThread diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt index 8569fc73adb4..14fc639c8aa8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor import android.annotation.WorkerThread diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt index ed7ec8e32de4..ffe38ddacfda 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/StateUpdateTrigger.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor import com.android.systemui.qs.tiles.viewmodel.QSTileState diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt index bb4de808de79..58a335e462a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/BaseQSTileViewModel.kt @@ -1,8 +1,26 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.viewmodel import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting import com.android.internal.util.Preconditions +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper @@ -10,10 +28,13 @@ import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.StateUpdateTrigger import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTileLifecycle +import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy import com.android.systemui.qs.tiles.viewmodel.QSTileState import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel import com.android.systemui.util.kotlin.sample +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -25,6 +46,7 @@ import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -37,40 +59,35 @@ 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 it's moved to * [QSTileLifecycle.ALIVE] state. + * + * Inject [BaseQSTileViewModel.Factory] to create a new instance of this class. */ -abstract class BaseQSTileViewModel<DATA_TYPE> +class BaseQSTileViewModel<DATA_TYPE> @VisibleForTesting constructor( - final override val config: QSTileConfig, + override val config: QSTileConfig, private val userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, private val tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, private val mapper: QSTileDataToStateMapper<DATA_TYPE>, + private val disabledByPolicyInteractor: DisabledByPolicyInteractor, private val backgroundDispatcher: CoroutineDispatcher, private val tileScope: CoroutineScope, ) : QSTileViewModel { - /** - * @param config contains all the static information (like TileSpec) about the tile. - * @param userActionInteractor encapsulates user input processing logic. Use it to start - * activities, show dialogs or otherwise update the tile state. - * @param tileDataInteractor provides [DATA_TYPE] and its availability. - * @param backgroundDispatcher is used to run the internal [DATA_TYPE] processing and call - * interactors methods. This should likely to be @Background CoroutineDispatcher. - * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View layer. - * It's called in [backgroundDispatcher], so it's safe to perform long running operations - * there. - */ + @AssistedInject constructor( - config: QSTileConfig, - userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, - tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, - mapper: QSTileDataToStateMapper<DATA_TYPE>, - backgroundDispatcher: CoroutineDispatcher, + @Assisted config: QSTileConfig, + @Assisted userActionInteractor: QSTileUserActionInteractor<DATA_TYPE>, + @Assisted tileDataInteractor: QSTileDataInteractor<DATA_TYPE>, + @Assisted mapper: QSTileDataToStateMapper<DATA_TYPE>, + disabledByPolicyInteractor: DisabledByPolicyInteractor, + @Background backgroundDispatcher: CoroutineDispatcher, ) : this( config, userActionInteractor, tileDataInteractor, mapper, + disabledByPolicyInteractor, backgroundDispatcher, CoroutineScope(SupervisorJob()) ) @@ -145,7 +162,7 @@ constructor( userIds .flatMapLatest { userId -> merge( - userInputFlow(), + userInputFlow(userId), forceUpdates.map { StateUpdateTrigger.ForceUpdate }, ) .onStart { emit(StateUpdateTrigger.InitialRequest) } @@ -172,14 +189,41 @@ constructor( replay = 1, // we only care about the most recent value ) - private fun userInputFlow(): Flow<StateUpdateTrigger> { + private fun userInputFlow(userId: Int): Flow<StateUpdateTrigger> { data class StateWithData<T>(val state: QSTileState, val data: T) + return when (config.policy) { + is QSTilePolicy.NoRestrictions -> userInputs + is QSTilePolicy.Restricted -> + userInputs.filter { + val result = + disabledByPolicyInteractor.isDisabled(userId, config.policy.userRestriction) + !disabledByPolicyInteractor.handlePolicyResult(result) + } // Skip the input until there is some data - return userInputs.sample( - state.combine(tileData) { state, data -> StateWithData(state, data) } - ) { input, stateWithData -> + }.sample(state.combine(tileData) { state, data -> StateWithData(state, data) }) { + input, + stateWithData -> StateUpdateTrigger.UserAction(input, stateWithData.state, stateWithData.data) } } + + interface Factory<T> { + + /** + * @param config contains all the static information (like TileSpec) about the tile. + * @param userActionInteractor encapsulates user input processing logic. Use it to start + * activities, show dialogs or otherwise update the tile state. + * @param tileDataInteractor provides [DATA_TYPE] and its availability. + * @param mapper maps [DATA_TYPE] to the [QSTileState] that is then displayed by the View + * layer. It's called in [backgroundDispatcher], so it's safe to perform long running + * operations there. + */ + fun create( + config: QSTileConfig, + userActionInteractor: QSTileUserActionInteractor<T>, + tileDataInteractor: QSTileDataInteractor<T>, + mapper: QSTileDataToStateMapper<T>, + ): BaseQSTileViewModel<T> + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 3fedbfc6671d..d0809c52acd9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.di import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt index 019d3c0ee416..1a6cf99ab810 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfig.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.viewmodel import androidx.annotation.StringRes @@ -10,4 +26,19 @@ data class QSTileConfig( val tileIcon: Icon, @StringRes val tileLabelRes: Int, val instanceId: InstanceId, + val policy: QSTilePolicy = QSTilePolicy.NoRestrictions, ) + +/** Represents policy restrictions that may be imposed on the tile. */ +sealed interface QSTilePolicy { + /** Tile has no policy restrictions */ + data object NoRestrictions : QSTilePolicy + + /** + * Tile might be disabled by policy. [userRestriction] is usually a constant from + * [android.os.UserManager] like [android.os.UserManager.DISALLOW_AIRPLANE_MODE]. + * [com.android.systemui.qs.tiles.base.interactor.DisabledByPolicyInteractor] is commonly used + * to resolve this and show user a message when needed. + */ + data class Restricted(val userRestriction: String) : QSTilePolicy +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt index 1d5c1bcada1f..6d7c57605bec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileLifecycle.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.viewmodel enum class QSTileLifecycle { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt index dc5c69080fd0..0ccde741e2cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.viewmodel import android.service.quicksettings.Tile diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt index 0b232c28d3f4..a1450420131b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileUserAction.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.viewmodel import android.view.View diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt index d66d0a195e02..e5cb7ea3e098 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.viewmodel import kotlinx.coroutines.flow.SharedFlow diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt index d4bdb77c2b41..f6299e38ae18 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.viewmodel import android.content.Context diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt index 077c81343c83..06b7a9fde873 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt @@ -1,11 +1,27 @@ -package com.android.systemui.qs.tiles.base +/* + * Copyright (C) 2023 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.qs.tiles.base.actions import android.content.Intent -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.systemui.RoboPilotTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserActionHandler import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -16,7 +32,8 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RoboPilotTest +@RunWith(AndroidJUnit4::class) class QSTileIntentUserActionHandlerTest : SysuiTestCase() { @Mock private lateinit var activityStarted: ActivityStarter diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt new file mode 100644 index 000000000000..4f25d12aea49 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/interactor/DisabledByPolicyInteractorTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.RestrictedLockUtils +import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin +import com.android.systemui.RoboPilotTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Captor +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@SmallTest +@RoboPilotTest +@RunWith(AndroidJUnit4::class) +class DisabledByPolicyInteractorTest : SysuiTestCase() { + + @Mock private lateinit var restrictedLockProxy: RestrictedLockProxy + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var context: Context + + @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent> + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + lateinit var underTest: DisabledByPolicyInteractor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + underTest = + DisabledByPolicyInteractorImpl( + context, + activityStarter, + restrictedLockProxy, + testDispatcher, + ) + } + + @Test + fun testEnabledWhenNoAdmin() = + testScope.runTest { + whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(null) + + assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION)) + .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled) + } + + @Test + fun testDisabledWhenAdminWithNoRestrictions() = + testScope.runTest { + val admin = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER)) + whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(admin) + whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString())) + .thenReturn(false) + + val result = + underTest.isDisabled(TEST_USER, TEST_RESTRICTION) + as DisabledByPolicyInteractor.PolicyResult.TileDisabled + assertThat(result.admin).isEqualTo(admin) + } + + @Test + fun testEnabledWhenAdminWithRestrictions() = + testScope.runTest { + whenever(restrictedLockProxy.getEnforcedAdmin(anyInt(), anyString())).thenReturn(ADMIN) + whenever(restrictedLockProxy.hasBaseUserRestriction(anyInt(), anyString())) + .thenReturn(true) + + assertThat(underTest.isDisabled(TEST_USER, TEST_RESTRICTION)) + .isSameInstanceAs(DisabledByPolicyInteractor.PolicyResult.TileEnabled) + } + + @Test + fun testHandleDisabledByPolicy() { + val result = + underTest.handlePolicyResult( + DisabledByPolicyInteractor.PolicyResult.TileDisabled(ADMIN) + ) + + val expectedIntent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(context, ADMIN) + assertThat(result).isTrue() + verify(activityStarter).postStartActivityDismissingKeyguard(intentCaptor.capture(), any()) + assertThat(intentCaptor.value.filterEquals(expectedIntent)).isTrue() + } + + @Test + fun testHandleEnabled() { + val result = + underTest.handlePolicyResult(DisabledByPolicyInteractor.PolicyResult.TileEnabled) + + assertThat(result).isFalse() + verify(activityStarter, never()) + .postStartActivityDismissingKeyguard(intentCaptor.capture(), any()) + } + + private companion object { + const val TEST_USER = 1 + const val TEST_RESTRICTION = "test_restriction" + + val TEST_COMPONENT_NAME = ComponentName("test.pkg", "test.cls") + + val ADMIN = EnforcedAdmin(TEST_COMPONENT_NAME, UserHandle(TEST_USER)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt index eacb08010159..9024c6c5576b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelInterfaceComplianceTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.qs.tiles.viewmodel import android.graphics.drawable.ShapeDrawable -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import com.android.internal.logging.InstanceId import com.android.systemui.RoboPilotTest @@ -10,6 +10,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription import com.android.systemui.common.shared.model.Icon import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.qs.tiles.base.interactor.FakeDisabledByPolicyInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileDataInteractor import com.android.systemui.qs.tiles.base.interactor.FakeQSTileUserActionInteractor import com.android.systemui.qs.tiles.base.interactor.QSTileDataRequest @@ -29,12 +30,13 @@ import org.junit.runner.RunWith // TODO(b/299909368): Add more tests @MediumTest @RoboPilotTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { private val fakeQSTileDataInteractor = FakeQSTileDataInteractor<Any>() private val fakeQSTileUserActionInteractor = FakeQSTileUserActionInteractor<Any>() + private val fakeDisabledByPolicyInteractor = FakeDisabledByPolicyInteractor() private val testCoroutineDispatcher = StandardTestDispatcher() private val testScope = TestScope(testCoroutineDispatcher) @@ -68,18 +70,18 @@ class QSTileViewModelInterfaceComplianceTest : SysuiTestCase() { scope: TestScope, config: QSTileConfig = TEST_QS_TILE_CONFIG, ): QSTileViewModel = - object : - BaseQSTileViewModel<Any>( - config, - fakeQSTileUserActionInteractor, - fakeQSTileDataInteractor, - object : QSTileDataToStateMapper<Any> { - override fun map(config: QSTileConfig, data: Any): QSTileState = - QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} - }, - testCoroutineDispatcher, - tileScope = scope.backgroundScope, - ) {} + BaseQSTileViewModel( + config, + fakeQSTileUserActionInteractor, + fakeQSTileDataInteractor, + object : QSTileDataToStateMapper<Any> { + override fun map(config: QSTileConfig, data: Any): QSTileState = + QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {} + }, + fakeDisabledByPolicyInteractor, + testCoroutineDispatcher, + scope.backgroundScope, + ) private companion object { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt new file mode 100644 index 000000000000..f62bf6014374 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeDisabledByPolicyInteractor.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor + +class FakeDisabledByPolicyInteractor : DisabledByPolicyInteractor { + + var handleResult: Boolean = false + var policyResult: DisabledByPolicyInteractor.PolicyResult = + DisabledByPolicyInteractor.PolicyResult.TileEnabled + + override suspend fun isDisabled( + userId: Int, + userRestriction: String? + ): DisabledByPolicyInteractor.PolicyResult = policyResult + + override fun handlePolicyResult( + policyResult: DisabledByPolicyInteractor.PolicyResult + ): Boolean = handleResult +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt index 13437c925367..1cb4ab76c9d5 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileDataInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor import javax.annotation.CheckReturnValue diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt index 4e0266e25716..9c99cb52d5ee 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/base/interactor/FakeQSTileUserActionInteractor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2023 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.qs.tiles.base.interactor import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction |