Add disabled by policy state
Test: atest DisabledByPolicyInteractorTest
Bug: 299908705
Change-Id: Id0636fe8fad326572384c9f187c9b5da7e060872
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 104f3d2..ee05f2d 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -363,6 +363,8 @@
"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 21aaa94..b50798e 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.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 @@
): 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 dc9e115..9d10072 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 0000000..056f967
--- /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 1a03481..7a22e3c 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 8289704..0aa6b0b 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 d6c9705..2bc6643 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 8569fc7..14fc639 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 ed7ec8e..ffe38dd 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 bb4de80..58a335e 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.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.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 @@
* 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 @@
userIds
.flatMapLatest { userId ->
merge(
- userInputFlow(),
+ userInputFlow(userId),
forceUpdates.map { StateUpdateTrigger.ForceUpdate },
)
.onStart { emit(StateUpdateTrigger.InitialRequest) }
@@ -172,14 +189,41 @@
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 3fedbfc..d0809c5 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 019d3c0..1a6cf99 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 @@
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 1d5c1bc..6d7c576 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 dc5c690..0ccde74 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 0b232c2..a145042 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 d66d0a1..e5cb7ea 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 d4bdb77..f6299e3 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/QSTileIntentUserActionHandlerTest.kt
deleted file mode 100644
index 077c813..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/QSTileIntentUserActionHandlerTest.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package com.android.systemui.qs.tiles.base
-
-import android.content.Intent
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-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
-import org.mockito.Mock
-import org.mockito.Mockito.any
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
-
- @Mock private lateinit var activityStarted: ActivityStarter
-
- lateinit var underTest: QSTileIntentUserActionHandler
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- underTest = QSTileIntentUserActionHandler(activityStarted)
- }
-
- @Test
- fun testPassesIntentToStarter() {
- val intent = Intent("test.ACTION")
-
- underTest.handle(null, intent)
-
- verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
new file mode 100644
index 0000000..06b7a9f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserActionHandlerTest.kt
@@ -0,0 +1,57 @@
+/*
+ * 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 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 org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class QSTileIntentUserActionHandlerTest : SysuiTestCase() {
+
+ @Mock private lateinit var activityStarted: ActivityStarter
+
+ lateinit var underTest: QSTileIntentUserActionHandler
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest = QSTileIntentUserActionHandler(activityStarted)
+ }
+
+ @Test
+ fun testPassesIntentToStarter() {
+ val intent = Intent("test.ACTION")
+
+ underTest.handle(null, intent)
+
+ verify(activityStarted).postStartActivityDismissingKeyguard(eq(intent), eq(0), any())
+ }
+}
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 0000000..4f25d12
--- /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 eacb080..9024c6c 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.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 @@
// 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 @@
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 0000000..f62bf60
--- /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 13437c9..1cb4ab7 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 4e0266e..9c99cb5 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