Add a TableLog for hydrator

This adds an optional TableLog for hydrator to allow logging of changing
states.

As a first use, add it to QSFragmentComposeViewModel.

Flag: EXEMPT logging
Fixes: 379669380
Test: manual, dump QSFragmentComposeViewModel

Change-Id: Ie103fa01d36d54a9f02ad2f25dd521ab25183ae2
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
index 881228d..93ecae3 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt
@@ -21,11 +21,11 @@
 import androidx.compose.runtime.snapshots.StateFactoryMarker
 import com.android.app.tracing.coroutines.launchTraced as launch
 import com.android.app.tracing.coroutines.traceCoroutine
+import com.android.systemui.log.table.TableLogBuffer
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
 
 /**
  * Keeps snapshot/Compose [State]s up-to-date.
@@ -47,6 +47,12 @@
      * concatenation or templating.
      */
     private val traceName: String,
+    /**
+     * An optional [TableLogBuffer] to log emissions to the states. [traceName] will be used as the
+     * prefix for the columns logged by this [Hydrator], allowing to aggregate multiple hydrators in
+     * the same table.
+     */
+    private val tableLogBuffer: TableLogBuffer? = null,
 ) : ExclusiveActivatable() {
 
     private val children = mutableListOf<NamedActivatable>()
@@ -62,15 +68,8 @@
      *   automatically set on the returned [State].
      */
     @StateFactoryMarker
-    fun <T> hydratedStateOf(
-        traceName: String,
-        source: StateFlow<T>,
-    ): State<T> {
-        return hydratedStateOf(
-            traceName = traceName,
-            initialValue = source.value,
-            source = source,
-        )
+    fun <T> hydratedStateOf(traceName: String, source: StateFlow<T>): State<T> {
+        return hydratedStateOf(traceName = traceName, initialValue = source.value, source = source)
     }
 
     /**
@@ -81,26 +80,44 @@
      *   performance findings with actual code. One recommendation: prefer whole string literals
      *   instead of some complex concatenation or templating scheme. Use `null` to disable
      *   performance tracing for this state.
+     *
+     *   If a [TableLogBuffer] was provided, every emission to the flow will be logged using the
+     *   [traceName] as the column name. For this to work correctly, all the states in the same
+     *   hydrator should have different [traceName]. Use `null` to disable logging for this state.
+     *
      * @param initialValue The first value to place on the [State]
      * @param source The upstream [Flow] to collect from; values emitted to it will be automatically
      *   set on the returned [State].
      */
     @StateFactoryMarker
-    fun <T> hydratedStateOf(
-        traceName: String?,
-        initialValue: T,
-        source: Flow<T>,
-    ): State<T> {
+    fun <T> hydratedStateOf(traceName: String?, initialValue: T, source: Flow<T>): State<T> {
         check(!isActive) { "Cannot call hydratedStateOf after Hydrator is already active." }
 
         val mutableState = mutableStateOf(initialValue)
+        traceName?.let { name ->
+            tableLogBuffer?.logChange(
+                prefix = this.traceName,
+                columnName = name,
+                value = initialValue?.toString(),
+                isInitial = true,
+            )
+        }
         children.add(
             NamedActivatable(
                 traceName = traceName,
                 activatable =
                     object : ExclusiveActivatable() {
                         override suspend fun onActivated(): Nothing {
-                            source.collect { mutableState.value = it }
+                            source.collect {
+                                traceName?.let { name ->
+                                    tableLogBuffer?.logChange(
+                                        prefix = this@Hydrator.traceName,
+                                        columnName = name,
+                                        value = it?.toString(),
+                                    )
+                                }
+                                mutableState.value = it
+                            }
                             awaitCancellation()
                         }
                     },
@@ -122,8 +139,5 @@
         }
     }
 
-    private data class NamedActivatable(
-        val traceName: String?,
-        val activatable: Activatable,
-    )
+    private data class NamedActivatable(val traceName: String?, val activatable: Activatable)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeLog.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeLog.kt
new file mode 100644
index 0000000..5f151eb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeLog.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 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.composefragment.dagger
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class QSFragmentComposeLog
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
index 2ec7292..bea0d14 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/dagger/QSFragmentComposeModule.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.qs.flags.QSComposeFragment
 import com.android.systemui.shade.ShadeDisplayAware
 import com.android.systemui.util.Utils
@@ -38,5 +40,14 @@
         fun providesUsingMedia(@ShadeDisplayAware context: Context): Boolean {
             return QSComposeFragment.isEnabled && Utils.useQsMediaPlayer(context)
         }
+
+        @Provides
+        @SysUISingleton
+        @QSFragmentComposeLog
+        fun providesQSFragmentComposeViewModelTableLog(
+            factory: TableLogBufferFactory
+        ): TableLogBuffer {
+            return factory.create("QSFragmentComposeViewModel", 200)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
index e3de6d5..5c582ba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -39,6 +39,7 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.lifecycle.ExclusiveActivatable
 import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QQS
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager.Companion.LOCATION_QS
@@ -48,6 +49,7 @@
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeLog
 import com.android.systemui.qs.composefragment.dagger.QSFragmentComposeModule
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
 import com.android.systemui.qs.panels.domain.interactor.TileSquishinessInteractor
@@ -101,6 +103,7 @@
     private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
     private val squishinessInteractor: TileSquishinessInteractor,
     private val inFirstPageViewModel: InFirstPageViewModel,
+    @QSFragmentComposeLog private val tableLogBuffer: TableLogBuffer,
     mediaInRowInLandscapeViewModelFactory: MediaInRowInLandscapeViewModel.Factory,
     @Named(QUICK_QS_PANEL) val qqsMediaHost: MediaHost,
     @Named(QS_PANEL) val qsMediaHost: MediaHost,
@@ -112,7 +115,7 @@
     private val qqsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QQS)
     private val qsMediaInRowViewModel = mediaInRowInLandscapeViewModelFactory.create(LOCATION_QS)
 
-    private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator")
+    private val hydrator = Hydrator("QSFragmentComposeViewModel.hydrator", tableLogBuffer)
 
     val footerActionsViewModel =
         footerActionsViewModelFactory.create(lifecycleScope).also {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
index 45d5b38..80db0ae 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.table.logcatTableLogBuffer
 import com.android.systemui.media.controls.ui.view.qqsMediaHost
 import com.android.systemui.media.controls.ui.view.qsMediaHost
 import com.android.systemui.qs.composefragment.dagger.usingMediaInComposeFragment
@@ -58,6 +59,7 @@
                     largeScreenHeaderHelper,
                     tileSquishinessInteractor,
                     inFirstPageViewModel,
+                    logcatTableLogBuffer(this@Fixture),
                     mediaInRowInLandscapeViewModelFactory,
                     qqsMediaHost,
                     qsMediaHost,