Add SpaLogger.

Define SpaLogger interface.
Add SpaLogger in SpaEnvironment.
Add Page enter / leave event in BrowseActivity
Use SpaLogger in Gallery Pages for logging.

Bug: 244122804
Test: manual - build Gallery
Change-Id: Id1df1760a0ddbf13f6a10ef3ef42b28fd398542f
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index d154dc1..acb22da 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.gallery
 
+import com.android.settingslib.spa.framework.common.LocalLogger
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.common.createSettingsPage
@@ -75,4 +76,6 @@
     override val browseActivityClass = GalleryMainActivity::class.java
 
     override val entryProviderAuthorities = "com.android.spa.gallery.provider"
+
+    override val logger = LocalLogger()
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index 107d3f3..e5e3c67 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -17,13 +17,13 @@
 package com.android.settingslib.spa.gallery.page
 
 import android.os.Bundle
-import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.lifecycle.viewmodel.compose.viewModel
 import androidx.navigation.NavType
 import androidx.navigation.navArgument
 import com.android.settingslib.spa.framework.common.EntrySearchData
 import com.android.settingslib.spa.framework.common.PageModel
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.util.getIntArg
@@ -32,6 +32,8 @@
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 
+private const val TAG = "ArgumentPageModel"
+
 // Defines all the resources for this page.
 // In real Settings App, resources data is defined in xml, rather than SPP.
 private const val PAGE_TITLE = "Sample page with arguments"
@@ -93,7 +95,9 @@
     private var intParam: Int? = null
 
     override fun initialize(arguments: Bundle?) {
-        logMsg("init with args " + arguments.toString())
+        SpaEnvironmentFactory.instance.logger.message(
+            TAG, "Initialize with args " + arguments.toString()
+        )
         this.arguments = arguments
         stringParam = parameter.getStringArg(STRING_PARAM_NAME, arguments)
         intParam = parameter.getIntArg(INT_PARAM_NAME, arguments)
@@ -135,7 +139,3 @@
         }
     }
 }
-
-private fun logMsg(message: String) {
-    Log.d("ArgumentPageModel", message)
-}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index f19e9a3..a2a913f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -30,6 +30,7 @@
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.toState
 import com.android.settingslib.spa.framework.theme.SettingsTheme
@@ -44,13 +45,14 @@
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_KEYWORDS
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_SUMMARY
 import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.SIMPLE_PREFERENCE_TITLE
-import com.android.settingslib.spa.gallery.preference.PreferencePageModel.Companion.logMsg
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.SettingsIcon
 
+private const val TAG = "PreferencePage"
+
 object PreferencePageProvider : SettingsPageProvider {
     // Defines all entry name in this page.
     // Note that entry name would be used in log. DO NOT change it once it is set.
@@ -67,6 +69,7 @@
 
     override val name = SettingsPageProviderEnum.PREFERENCE.name
     override val displayName = SettingsPageProviderEnum.PREFERENCE.displayName
+    private val spaLogger = SpaEnvironmentFactory.instance.logger
     private val owner = createSettingsPage()
 
     private fun createEntry(entry: EntryEnum): SettingsEntryBuilder {
@@ -79,7 +82,7 @@
             createEntry(EntryEnum.SIMPLE_PREFERENCE)
                 .setIsAllowSearch(true)
                 .setMacro {
-                    logMsg("create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
+                    spaLogger.message(TAG, "create macro for ${EntryEnum.SIMPLE_PREFERENCE}")
                     SimplePreferenceMacro(title = SIMPLE_PREFERENCE_TITLE)
                 }
                 .build()
@@ -88,7 +91,7 @@
             createEntry(EntryEnum.SUMMARY_PREFERENCE)
                 .setIsAllowSearch(true)
                 .setMacro {
-                    logMsg("create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
+                    spaLogger.message(TAG, "create macro for ${EntryEnum.SUMMARY_PREFERENCE}")
                     SimplePreferenceMacro(
                         title = SIMPLE_PREFERENCE_TITLE,
                         summary = SIMPLE_PREFERENCE_SUMMARY,
@@ -102,7 +105,7 @@
             createEntry(EntryEnum.DISABLED_PREFERENCE)
                 .setIsAllowSearch(true)
                 .setMacro {
-                    logMsg("create macro for ${EntryEnum.DISABLED_PREFERENCE}")
+                    spaLogger.message(TAG, "create macro for ${EntryEnum.DISABLED_PREFERENCE}")
                     SimplePreferenceMacro(
                         title = DISABLE_PREFERENCE_TITLE,
                         summary = DISABLE_PREFERENCE_SUMMARY,
@@ -188,7 +191,7 @@
         return SettingsEntryBuilder.createInject(owner = owner)
             .setIsAllowSearch(true)
             .setMacro {
-                logMsg("create macro for INJECT entry")
+                spaLogger.message(TAG, "create macro for INJECT entry")
                 SimplePreferenceMacro(
                     title = PAGE_TITLE,
                     clickRoute = SettingsPageProviderEnum.PREFERENCE.name
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index 1188e1e..1e64b2e 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -17,7 +17,6 @@
 package com.android.settingslib.spa.gallery.preference
 
 import android.os.Bundle
-import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
@@ -27,11 +26,14 @@
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.settingslib.spa.framework.common.PageModel
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 
+private const val TAG = "PreferencePageModel"
+
 class PreferencePageModel : PageModel() {
     companion object {
         // Defines all the resources for this page.
@@ -53,12 +55,10 @@
             pageModel.initOnce()
             return pageModel
         }
-
-        fun logMsg(message: String) {
-            Log.d("PreferencePageModel", message)
-        }
     }
 
+    private val spaLogger = SpaEnvironmentFactory.instance.logger
+
     private val asyncSummary = mutableStateOf(" ")
 
     private val manualUpdater = mutableStateOf(0)
@@ -67,26 +67,25 @@
         private var tick = 0
         private var updateJob: Job? = null
         override fun onActive() {
-            logMsg("autoUpdater.active")
+            spaLogger.message(TAG, "autoUpdater.active")
             updateJob = viewModelScope.launch(Dispatchers.IO) {
                 while (true) {
                     delay(1000L)
                     tick++
-                    logMsg("autoUpdater.value $tick")
+                    spaLogger.message(TAG, "autoUpdater.value $tick")
                     postValue(tick.toString())
                 }
             }
         }
 
         override fun onInactive() {
-            logMsg("autoUpdater.inactive")
+            spaLogger.message(TAG, "autoUpdater.inactive")
             updateJob?.cancel()
         }
     }
 
     override fun initialize(arguments: Bundle?) {
-        logMsg("init with args " + arguments.toString())
-
+        spaLogger.message(TAG, "initialize with args " + arguments.toString())
         viewModelScope.launch(Dispatchers.IO) {
             delay(2000L)
             asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
@@ -94,22 +93,22 @@
     }
 
     fun getAsyncSummary(): State<String> {
-        logMsg("getAsyncSummary")
+        spaLogger.message(TAG, "getAsyncSummary")
         return asyncSummary
     }
 
     fun getManualUpdaterSummary(): State<String> {
-        logMsg("getManualUpdaterSummary")
+        spaLogger.message(TAG, "getManualUpdaterSummary")
         return derivedStateOf { manualUpdater.value.toString() }
     }
 
     fun manualUpdaterOnClick() {
-        logMsg("manualUpdaterOnClick")
+        spaLogger.message(TAG, "manualUpdaterOnClick")
         manualUpdater.value = manualUpdater.value + 1
     }
 
     fun getAutoUpdaterSummary(): LiveData<String> {
-        logMsg("getAutoUpdaterSummary")
+        spaLogger.message(TAG, "getAutoUpdaterSummary")
         return autoUpdater
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index d87c31b..f9d2dbc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -17,20 +17,25 @@
 package com.android.settingslib.spa.framework
 
 import android.os.Bundle
-import android.util.Log
 import androidx.activity.ComponentActivity
 import androidx.activity.compose.setContent
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
 import androidx.navigation.NavGraph.Companion.findStartDestination
 import androidx.navigation.compose.NavHost
 import androidx.navigation.compose.composable
 import androidx.navigation.compose.rememberNavController
 import com.android.settingslib.spa.R
+import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.LocalNavController
 import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
 import com.android.settingslib.spa.framework.compose.localNavController
@@ -56,7 +61,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
-        Log.d(TAG, "onCreate")
+        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
 
         setContent {
             SettingsTheme {
@@ -72,11 +77,43 @@
         CompositionLocalProvider(navController.localNavController()) {
             NavHost(navController, NULL_PAGE_NAME) {
                 composable(NULL_PAGE_NAME) {}
-                for (page in sppRepository.getAllProviders()) {
+                for (spp in sppRepository.getAllProviders()) {
                     composable(
-                        route = page.name + page.parameter.navRoute(),
-                        arguments = page.parameter,
-                    ) { navBackStackEntry -> page.Page(navBackStackEntry.arguments) }
+                        route = spp.name + spp.parameter.navRoute(),
+                        arguments = spp.parameter,
+                    ) { navBackStackEntry ->
+                        val lifecycleOwner = LocalLifecycleOwner.current
+                        val spaLogger = spaEnvironment.logger
+                        val sp = spp.createSettingsPage(arguments = navBackStackEntry.arguments)
+
+                        DisposableEffect(lifecycleOwner) {
+                            val observer = LifecycleEventObserver { _, event ->
+                                if (event == Lifecycle.Event.ON_START) {
+                                    spaLogger.event(
+                                        sp.id,
+                                        "enter page ${sp.formatDisplayTitle()}",
+                                        category = LogCategory.FRAMEWORK
+                                    )
+                                } else if (event == Lifecycle.Event.ON_STOP) {
+                                    spaLogger.event(
+                                        sp.id,
+                                        "leave page ${sp.formatDisplayTitle()}",
+                                        category = LogCategory.FRAMEWORK
+                                    )
+                                }
+                            }
+
+                            // Add the observer to the lifecycle
+                            lifecycleOwner.lifecycle.addObserver(observer)
+
+                            // When the effect leaves the Composition, remove the observer
+                            onDispose {
+                                lifecycleOwner.lifecycle.removeObserver(observer)
+                            }
+                        }
+
+                        spp.Page(navBackStackEntry.arguments)
+                    }
                 }
             }
             InitialDestinationNavigator()
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
index b28da06..6f96818 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
@@ -35,6 +35,7 @@
 import com.android.settingslib.spa.R
 import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
 import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_HIGHLIGHT_ENTRY
+import com.android.settingslib.spa.framework.common.LogCategory
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
@@ -69,7 +70,7 @@
     override fun onCreate(savedInstanceState: Bundle?) {
         setTheme(R.style.Theme_SpaLib_DayNight)
         super.onCreate(savedInstanceState)
-        Log.d(TAG, "onCreate")
+        spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
 
         setContent {
             SettingsTheme {
@@ -94,7 +95,7 @@
                         cursor.getBoolean(query, EntryProvider.ColumnEnum.HAS_RUNTIME_PARAM)
                     val message = "Page Info: $route ($entryCount) " +
                         (if (hasRuntimeParam) "with" else "no") + "-runtime-params"
-                    Log.d(TAG, message)
+                    spaEnvironment.logger.message(TAG, message, category = LogCategory.FRAMEWORK)
                 }
             }
         } catch (e: Exception) {
@@ -229,7 +230,9 @@
             putExtra(KEY_DESTINATION, route)
         }
         return {
-            Log.d(TAG, "OpenPage: $route")
+            spaEnvironment.logger.message(
+                TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK
+            )
             context.startActivity(intent)
         }
     }
@@ -244,7 +247,9 @@
             putExtra(KEY_HIGHLIGHT_ENTRY, entry.id)
         }
         return {
-            Log.d(TAG, "OpenEntry: $route")
+            spaEnvironment.logger.message(
+                TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK
+            )
             context.startActivity(intent)
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index f762f6e..e0835ea 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -48,5 +48,7 @@
 
     open val entryProviderAuthorities: String? = null
 
+    open val logger: SpaLogger = object : SpaLogger {}
+
     // TODO: add other environment setup here.
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
new file mode 100644
index 0000000..5efedec
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import android.util.Log
+
+// Defines the category of the log, for quick filter
+enum class LogCategory {
+    // The default category, for logs from Pages & their Models.
+    DEFAULT,
+
+    // For logs from Spa Framework, such as BrowseActivity, EntryProvider
+    FRAMEWORK,
+
+    // For logs from Spa UI components, such as Widgets, Scaffold
+    VIEW,
+}
+
+/**
+ * The interface of logger in Spa
+ */
+interface SpaLogger {
+    // log a message, usually for debug purpose.
+    fun message(tag: String, msg: String, category: LogCategory = LogCategory.DEFAULT) {}
+
+    // log a user event.
+    fun event(id: String, event: String, category: LogCategory = LogCategory.DEFAULT) {}
+}
+
+class LocalLogger : SpaLogger {
+    override fun message(tag: String, msg: String, category: LogCategory) {
+        Log.d("SpaMsg-$category", "[$tag] $msg")
+    }
+
+    override fun event(id: String, event: String, category: LogCategory) {
+        Log.d("SpaEvent-$category", "[$id] $event")
+    }
+}
\ No newline at end of file