Merge "Update NewQSTileFactory and QSTileConfigProvider to provide custom tile ViewModel" into main
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
index 736f7cf..ae554d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelFactory.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.logging.QSTileLogger
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import com.android.systemui.user.data.repository.UserRepository
@@ -60,12 +61,17 @@
     ) : QSTileViewModelFactory<T> {
 
         /**
-         * Creates [QSTileViewModelImpl] based on the interactors obtained from [component].
-         * Reference of that [component] is then stored along the view model.
+         * Creates [QSTileViewModelImpl] based on the interactors obtained from [QSTileComponent].
+         * Reference of that [QSTileComponent] is then stored along the view model.
          */
-        fun create(tileSpec: TileSpec, component: QSTileComponent<T>): QSTileViewModelImpl<T> =
-            QSTileViewModelImpl(
-                qsTileConfigProvider.getConfig(tileSpec.spec),
+        fun create(
+            tileSpec: TileSpec,
+            componentFactory: (config: QSTileConfig) -> QSTileComponent<T>
+        ): QSTileViewModelImpl<T> {
+            val config = qsTileConfigProvider.getConfig(tileSpec.spec)
+            val component = componentFactory(config)
+            return QSTileViewModelImpl(
+                config,
                 component::userActionInteractor,
                 component::dataInteractor,
                 component::dataToStateMapper,
@@ -77,6 +83,7 @@
                 systemClock,
                 backgroundDispatcher,
             )
+        }
     }
 
     /**
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 7d7af64..27007bb 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
@@ -19,6 +19,11 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.qs.QSFactory
 import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent
+import com.android.systemui.qs.tiles.impl.custom.di.QSTileConfigModule
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModelAdapter
@@ -34,18 +39,31 @@
     private val adapterFactory: QSTileViewModelAdapter.Factory,
     private val tileMap:
         Map<String, @JvmSuppressWildcards Provider<@JvmSuppressWildcards QSTileViewModel>>,
+    private val customTileComponentBuilder: CustomTileComponent.Builder,
+    private val customTileViewModelFactory: QSTileViewModelFactory.Component<CustomTileDataModel>,
 ) : QSFactory {
 
     init {
         for (viewModelTileSpec in tileMap.keys) {
-            // throws an exception when there is no config for a tileSpec of an injected viewModel
-            qsTileConfigProvider.getConfig(viewModelTileSpec)
+            require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) {
+                "No config for $viewModelTileSpec"
+            }
         }
     }
 
-    override fun createTile(tileSpec: String): QSTile? =
-        tileMap[tileSpec]?.let {
-            val tile = it.get()
-            adapterFactory.create(tile)
+    override fun createTile(tileSpec: String): QSTile? {
+        val viewModel: QSTileViewModel =
+            when (val spec = TileSpec.create(tileSpec)) {
+                is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
+                is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
+                is TileSpec.Invalid -> null
+            }
+                ?: return null
+        return adapterFactory.create(viewModel)
+    }
+
+    private fun createCustomTileViewModel(spec: TileSpec.CustomTileSpec): QSTileViewModel =
+        customTileViewModelFactory.create(spec) { config ->
+            customTileComponentBuilder.qsTileConfigModule(QSTileConfigModule(config)).build()
         }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
index 761274e..14bf25d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
@@ -19,17 +19,18 @@
 import android.os.UserHandle
 import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
 @QSTileScope
-class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileData> {
+class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileDataModel> {
 
     override fun tileData(
         user: UserHandle,
         triggers: Flow<DataUpdateTrigger>
-    ): Flow<CustomTileData> {
+    ): Flow<CustomTileDataModel> {
         TODO("Not yet implemented")
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
index f7bec02..e23a5c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
@@ -17,15 +17,16 @@
 package com.android.systemui.qs.tiles.impl.custom
 
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
 import com.android.systemui.qs.tiles.viewmodel.QSTileState
 import javax.inject.Inject
 
 @QSTileScope
-class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileData> {
+class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileDataModel> {
 
-    override fun map(config: QSTileConfig, data: CustomTileData): QSTileState {
+    override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
         TODO("Not yet implemented")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
index 6c1c1a3..f34704b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
@@ -18,14 +18,15 @@
 
 import com.android.systemui.qs.tiles.base.interactor.QSTileInput
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import javax.inject.Inject
 
 @QSTileScope
 class CustomTileUserActionInteractor @Inject constructor() :
-    QSTileUserActionInteractor<CustomTileData> {
+    QSTileUserActionInteractor<CustomTileDataModel> {
 
-    override suspend fun handleInput(input: QSTileInput<CustomTileData>) {
+    override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) {
         TODO("Not yet implemented")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
index 01df906..88bc8fa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
@@ -16,13 +16,14 @@
 
 package com.android.systemui.qs.tiles.impl.custom.di
 
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import dagger.Subcomponent
 
 @QSTileScope
 @Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class])
-interface CustomTileComponent : QSTileComponent<Any> {
+interface CustomTileComponent : QSTileComponent<CustomTileDataModel> {
 
     @Subcomponent.Builder
     interface Builder {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
index 482bf9b..83767aa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
@@ -19,13 +19,13 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.impl.custom.CustomTileData
 import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor
 import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper
 import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl
 import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
 import dagger.Binds
 import dagger.Module
 
@@ -36,15 +36,15 @@
     @Binds
     fun bindDataInteractor(
         dataInteractor: CustomTileInteractor
-    ): QSTileDataInteractor<CustomTileData>
+    ): QSTileDataInteractor<CustomTileDataModel>
 
     @Binds
     fun bindUserActionInteractor(
         userActionInteractor: CustomTileUserActionInteractor
-    ): QSTileUserActionInteractor<CustomTileData>
+    ): QSTileUserActionInteractor<CustomTileDataModel>
 
     @Binds
-    fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileData>
+    fun bindMapper(customTileMapper: CustomTileMapper): QSTileDataToStateMapper<CustomTileDataModel>
 
     @Binds
     fun bindCustomTileDefaultsRepository(
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt
rename to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
index bb5a229..f095c01 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileData.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom
+package com.android.systemui.qs.tiles.impl.custom.domain.entity
 
 import android.content.ComponentName
 import android.graphics.drawable.Icon
@@ -22,12 +22,11 @@
 import android.service.quicksettings.Tile
 import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
 
-data class CustomTileData(
+data class CustomTileDataModel(
     val user: UserHandle,
     val componentName: ComponentName,
     val tile: Tile,
     val callingAppUid: Int,
-    val isActive: Boolean,
     val hasPendingBind: Boolean,
     val shouldShowChevron: Boolean,
     val defaultTileLabel: CharSequence?,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
index 3f3b94e..0609e79 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProvider.kt
@@ -18,20 +18,31 @@
 
 import com.android.internal.util.Preconditions
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import javax.inject.Inject
 
 interface QSTileConfigProvider {
 
     /**
-     * Returns a [QSTileConfig] for a [tileSpec] or throws [IllegalArgumentException] if there is no
-     * config for such [tileSpec].
+     * Returns a [QSTileConfig] for a [tileSpec]:
+     * - injected config for [TileSpec.PlatformTileSpec] or throws [IllegalArgumentException] if
+     *   there is none
+     * - new config for [TileSpec.CustomTileSpec].
+     * - throws [IllegalArgumentException] for [TileSpec.Invalid]
      */
     fun getConfig(tileSpec: String): QSTileConfig
+
+    fun hasConfig(tileSpec: String): Boolean
 }
 
 @SysUISingleton
-class QSTileConfigProviderImpl @Inject constructor(private val configs: Map<String, QSTileConfig>) :
-    QSTileConfigProvider {
+class QSTileConfigProviderImpl
+@Inject
+constructor(
+    private val configs: Map<String, QSTileConfig>,
+    private val qsEventLogger: QsEventLogger,
+) : QSTileConfigProvider {
 
     init {
         for (entry in configs.entries) {
@@ -44,6 +55,26 @@
         }
     }
 
+    override fun hasConfig(tileSpec: String): Boolean =
+        when (TileSpec.create(tileSpec)) {
+            is TileSpec.PlatformTileSpec -> configs.containsKey(tileSpec)
+            is TileSpec.CustomTileSpec -> true
+            is TileSpec.Invalid -> false
+        }
+
     override fun getConfig(tileSpec: String): QSTileConfig =
-        configs[tileSpec] ?: throw IllegalArgumentException("There is no config for spec=$tileSpec")
+        when (val spec = TileSpec.create(tileSpec)) {
+            is TileSpec.PlatformTileSpec -> {
+                configs[tileSpec]
+                    ?: throw IllegalArgumentException("There is no config for spec=$tileSpec")
+            }
+            is TileSpec.CustomTileSpec ->
+                QSTileConfig(
+                    spec,
+                    QSTileUIConfig.Empty,
+                    qsEventLogger.getNewInstanceId(),
+                )
+            is TileSpec.Invalid ->
+                throw IllegalArgumentException("TileSpec.Invalid doesn't support configs")
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
index 682b2d0..5eca8ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/viewmodel/QSTileConfigProviderTest.kt
@@ -19,7 +19,9 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -29,8 +31,8 @@
 class QSTileConfigProviderTest : SysuiTestCase() {
 
     private val underTest =
-        QSTileConfigProviderImpl(
-            mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC })
+        createQSTileConfigProviderImpl(
+            mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = VALID_SPEC }),
         )
 
     @Test
@@ -43,13 +45,31 @@
         underTest.getConfig(INVALID_SPEC.spec)
     }
 
+    @Test
+    fun hasConfigReturnsTrueForValidSpec() {
+        assertThat(underTest.hasConfig(VALID_SPEC.spec)).isTrue()
+    }
+
+    @Test
+    fun hasConfigReturnsFalseForInvalidSpec() {
+        assertThat(underTest.hasConfig(INVALID_SPEC.spec)).isFalse()
+    }
+
     @Test(expected = IllegalArgumentException::class)
     fun validatesSpecUponCreation() {
-        QSTileConfigProviderImpl(
+        createQSTileConfigProviderImpl(
             mapOf(VALID_SPEC.spec to QSTileConfigTestBuilder.build { tileSpec = INVALID_SPEC })
         )
     }
 
+    private fun createQSTileConfigProviderImpl(
+        configs: Map<String, QSTileConfig>
+    ): QSTileConfigProviderImpl =
+        QSTileConfigProviderImpl(
+            configs,
+            mock<QsEventLogger>(),
+        )
+
     private companion object {
 
         val VALID_SPEC = TileSpec.create("valid_tile_spec")
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
index de72a7d..d231d63 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/viewmodel/FakeQSTileConfigProvider.kt
@@ -24,6 +24,8 @@
 
     override fun getConfig(tileSpec: String): QSTileConfig = configs.getValue(tileSpec)
 
+    override fun hasConfig(tileSpec: String): Boolean = configs.containsKey(tileSpec)
+
     fun putConfig(tileSpec: TileSpec, config: QSTileConfig) {
         configs[tileSpec.spec] = config
     }