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