From d3a94cf4976f300ba25dc747b7bb882bef936018 Mon Sep 17 00:00:00 2001 From: Zekan Qian Date: Thu, 8 Sep 2022 16:13:53 +0800 Subject: [PATCH] Add debug page in gallery to show page & entry debug information. Bug: 244122804 Test: manual - build Spa gallery Change-Id: I4853e17028dfe0eed4342b4691962b508bc87357 --- .../Spa/gallery/AndroidManifest.xml | 6 +- .../spa/gallery/GalleryDebugActivity.kt | 21 +++ .../settingslib/spa/gallery/SpaEnvironment.kt | 2 +- .../settingslib/spa/gallery/home/HomePage.kt | 14 -- .../spa/framework/DebugActivity.kt | 155 ++++++++++++++++++ .../spa/framework/common/SettingsEntry.kt | 100 ++++++++--- .../common/SettingsEntryRepository.kt | 32 ++-- .../app/TogglePermissionAppInfoPage.kt | 2 +- .../app/TogglePermissionAppListPage.kt | 2 +- 9 files changed, 277 insertions(+), 57 deletions(-) create mode 100644 packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index 22299861e0e35..e5bf8ca60576e 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -29,6 +29,10 @@ - + + + diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt new file mode 100644 index 0000000000000..52bbf75e4ac23 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt @@ -0,0 +1,21 @@ +/* + * 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.gallery + +import com.android.settingslib.spa.framework.DebugActivity + +class GalleryDebugActivity : DebugActivity(SpaEnvironment.EntryRepository) diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt index e75e76c32ea90..6f675a300ce3e 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt @@ -51,7 +51,7 @@ object SpaEnvironment { IllustrationPageProvider, ), rootPages = listOf( - SettingsPage(HomePageProvider.name) + SettingsPage.create(HomePageProvider.name) ) + ArgumentPageProvider.buildRootPages() ) } diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt index 2428bba1dd4cb..7c57e75f0c404 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt @@ -17,15 +17,12 @@ package com.android.settingslib.spa.gallery.home import android.os.Bundle -import androidx.compose.material3.Button -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.gallery.R -import com.android.settingslib.spa.gallery.SpaEnvironment import com.android.settingslib.spa.gallery.page.ArgumentPageProvider import com.android.settingslib.spa.gallery.page.FooterPageProvider import com.android.settingslib.spa.gallery.page.IllustrationPageProvider @@ -55,17 +52,6 @@ private fun HomePage() { SettingsPagerPageProvider.EntryItem() FooterPageProvider.EntryItem() IllustrationPageProvider.EntryItem() - - /** - * A test button to generate hierarchy. - * TODO: remove it once the content provider is ready. - */ - Button(onClick = { - SpaEnvironment.EntryRepository.printAllPages() - SpaEnvironment.EntryRepository.printAllEntries() - }) { - Text(text = "Generate Entry") - } } } 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 new file mode 100644 index 0000000000000..d1ec7d0e6172a --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt @@ -0,0 +1,155 @@ +/* + * 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 + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.android.settingslib.spa.R +import com.android.settingslib.spa.framework.common.SettingsEntry +import com.android.settingslib.spa.framework.common.SettingsEntryRepository +import com.android.settingslib.spa.framework.compose.localNavController +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.compose.toState +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.HomeScaffold +import com.android.settingslib.spa.widget.scaffold.RegularScaffold + +private const val ROUTE_ROOT = "root" +private const val ROUTE_All_PAGES = "pages" +private const val ROUTE_All_ENTRIES = "entries" +private const val ROUTE_PAGE = "page" +private const val ROUTE_ENTRY = "entry" +private const val PARAM_NAME_PAGE_ID = "pid" +private const val PARAM_NAME_ENTRY_ID = "eid" + +open class DebugActivity( + private val entryRepository: SettingsEntryRepository +) : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(R.style.Theme_SpaLib_DayNight) + super.onCreate(savedInstanceState) + + setContent { + SettingsTheme { + MainContent() + } + } + } + + @Composable + private fun MainContent() { + val navController = rememberNavController() + CompositionLocalProvider(navController.localNavController()) { + NavHost(navController, ROUTE_ROOT) { + composable(route = ROUTE_ROOT) { RootPage() } + composable(route = ROUTE_All_PAGES) { AllPages() } + composable(route = ROUTE_All_ENTRIES) { AllEntries() } + composable( + route = "$ROUTE_PAGE/{$PARAM_NAME_PAGE_ID}", + arguments = listOf( + navArgument(PARAM_NAME_PAGE_ID) { type = NavType.IntType }, + ) + ) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) } + composable( + route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}", + arguments = listOf( + navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.IntType }, + ) + ) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) } + } + } + } + + @Composable + fun RootPage() { + HomeScaffold(title = "Entry Debug") { + Preference(object : PreferenceModel { + override val title = "List All Pages" + override val onClick = navigator(route = ROUTE_All_PAGES) + }) + Preference(object : PreferenceModel { + override val title = "List All Entries" + override val onClick = navigator(route = ROUTE_All_ENTRIES) + }) + } + } + + @Composable + fun AllPages() { + RegularScaffold(title = "All Pages") { + for (pageWithEntry in entryRepository.getAllPageWithEntry()) { + Preference(object : PreferenceModel { + override val title = + "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})" + override val summary = pageWithEntry.page.formatArguments().toState() + override val onClick = + navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}") + }) + } + } + } + + @Composable + fun AllEntries() { + RegularScaffold(title = "All Entries") { + EntryList(entryRepository.getAllEntries()) + } + } + + @Composable + fun OnePage(arguments: Bundle?) { + val id = arguments!!.getInt(PARAM_NAME_PAGE_ID) + val pageWithEntry = entryRepository.getPageWithEntry(id)!! + RegularScaffold(title = "Page ${pageWithEntry.page.displayName}") { + Text(text = pageWithEntry.page.formatArguments()) + Text(text = "Entry size: ${pageWithEntry.entries.size}") + EntryList(pageWithEntry.entries) + } + } + + @Composable + fun OneEntry(arguments: Bundle?) { + val id = arguments!!.getInt(PARAM_NAME_ENTRY_ID) + val entry = entryRepository.getEntry(id)!! + RegularScaffold(title = "Entry ${entry.displayName}") { + Text (text = entry.formatAll()) + } + } + + @Composable + private fun EntryList(entries: Collection) { + for (entry in entries) { + Preference(object : PreferenceModel { + override val title = entry.displayName + override val summary = + "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState() + override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}") + }) + } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt index 98734dac648ed..4452b819c1688 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt @@ -37,38 +37,57 @@ data class UiData(val title: String = "") /** * Defines data to identify a Settings page. */ -data class SettingsPage(val name: String = "", val arguments: Bundle? = null) { - override fun toString(): String { - val argsStr = arguments?.toString()?.removeRange(0, 6) ?: "" - return name + argsStr - } +data class SettingsPage( + // The unique id of this page, which is computed by name + arguments + val id: Int, + // The name of the page, which is used to compute the unique id, and need to be stable. + val name: String, + + // The display name of the page, for better readability. + // By default, it is the same as name. + val displayName: String, + + // The arguments of this page. + val arguments: Bundle? = null, +) { companion object { fun create( name: String, parameter: List = emptyList(), arguments: Bundle? = null ): SettingsPage { - return SettingsPage(name, parameter.normalize(arguments)) + return SettingsPageBuilder(name, parameter).setArguments(arguments).build() } } + + fun formatArguments(): String { + if (arguments == null || arguments.isEmpty) return "[No arguments]" + return arguments.toString().removeRange(0, 6) + } + + fun formatAll(): String { + return "$displayName ${formatArguments()}" + } } /** * Defines data of a Settings entry. */ data class SettingsEntry( - // The unique id of this entry. - // By default, it is computed by name + owner + fromPage + toPage - val id: String, - - // The display name of this entry, which is used to be shown in hierarchy. - // By default, it is computed by name + owner - val displayName: String, + // The unique id of this entry, which is computed by name + owner + fromPage + toPage. + val id: Int, + // The name of the page, which is used to compute the unique id, and need to be stable. val name: String, + + // The owner page of this entry. val owner: SettingsPage, + // The display name of the entry, for better readability. + // By default, it is $owner:$name + val displayName: String, + // Defines linking of Settings entries val fromPage: SettingsPage? = null, val toPage: SettingsPage? = null, @@ -106,8 +125,41 @@ data class SettingsEntry( */ val uiLayout: (@Composable () -> Unit) = {}, ) { - override fun toString(): String { - return displayName + "(${fromPage?.toString()}->${toPage?.toString()})" + fun formatAll(): String { + val content = listOf( + "owner = ${owner.formatAll()}", + "linkFrom = ${fromPage?.formatAll()}", + "linkTo = ${toPage?.formatAll()}", + ) + return content.joinToString("\n") + } +} + +data class SettingsPageWithEntry( + val page: SettingsPage, + val entries: List, +) + +class SettingsPageBuilder( + private val name: String, + private val parameter: List = emptyList() +) { + private var displayName: String? = null + private var arguments: Bundle? = null + + fun build(): SettingsPage { + val normArguments = parameter.normalize(arguments) + return SettingsPage( + id = "$name:${normArguments?.toString()}".toUniqueId(), + name = name, + displayName = displayName ?: name, + arguments = normArguments, + ) + } + + fun setArguments(arguments: Bundle?): SettingsPageBuilder { + this.arguments = arguments + return this } } @@ -115,7 +167,6 @@ data class SettingsEntry( * The helper to build a Settings Entry instance. */ class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) { - private var uniqueId: String? = null private var displayName: String? = null private var fromPage: SettingsPage? = null private var toPage: SettingsPage? = null @@ -126,8 +177,8 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings fun build(): SettingsEntry { return SettingsEntry( - id = computeUniqueId(), - displayName = computeDisplayName(), + id = "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toUniqueId(), + displayName = displayName ?: "${owner.displayName}:$name", name = name, owner = owner, @@ -136,7 +187,7 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings toPage = toPage, // attributes - isAllowSearch = computeSearchable(), + isAllowSearch = getIsSearchable(), // functions searchData = searchDataFn, @@ -168,12 +219,7 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings return this } - private fun computeUniqueId(): String = - uniqueId ?: "$owner:$name" + fromPage?.toString() + toPage?.toString() - - private fun computeDisplayName(): String = displayName ?: "$owner:$name" - - private fun computeSearchable(): Boolean = isAllowSearch ?: false + private fun getIsSearchable(): Boolean = isAllowSearch ?: false companion object { fun create(entryName: String, owner: SettingsPage): SettingsEntryBuilder { @@ -197,3 +243,7 @@ class SettingsEntryBuilder(private val name: String, private val owner: Settings } } } + +private fun String.toUniqueId(): Int { + return this.hashCode() +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt index e3a55e54c431f..c5f72c5e7b3bd 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt @@ -26,15 +26,15 @@ private const val MAX_ENTRY_SIZE = 5000 */ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) { // Map of entry unique Id to entry - private val entryMap: Map + private val entryMap: Map // Map of Settings page to its contained entries. - private val pageToEntryListMap: Map> + private val pageWithEntryMap: Map init { logMsg("Initialize") entryMap = mutableMapOf() - pageToEntryListMap = mutableMapOf() + pageWithEntryMap = mutableMapOf() val entryQueue = LinkedList() for (page in sppRepository.getAllRootPages()) { @@ -48,10 +48,10 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) { while (entryQueue.isNotEmpty() && entryMap.size < MAX_ENTRY_SIZE) { val entry = entryQueue.pop() val page = entry.toPage - if (page == null || pageToEntryListMap.containsKey(page.toString())) continue + if (page == null || pageWithEntryMap.containsKey(page.id)) continue val spp = sppRepository.getProviderOrNull(page.name) ?: continue val newEntries = spp.buildEntry(page.arguments) - pageToEntryListMap[page.toString()] = newEntries + pageWithEntryMap[page.id] = SettingsPageWithEntry(page, newEntries) for (newEntry in newEntries) { if (!entryMap.containsKey(newEntry.id)) { entryQueue.push(newEntry) @@ -60,19 +60,23 @@ class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) { } } - logMsg("Initialize Completed: ${entryMap.size} entries in ${pageToEntryListMap.size} pages") + logMsg("Initialize Completed: ${entryMap.size} entries in ${pageWithEntryMap.size} pages") } - fun printAllPages() { - for (entry in pageToEntryListMap.entries) { - logMsg("page: ${entry.key} with ${entry.value.size} entries") - } + fun getAllPageWithEntry(): Collection { + return pageWithEntryMap.values } - fun printAllEntries() { - for (entry in entryMap.values) { - logMsg("entry: $entry") - } + fun getPageWithEntry(pageId: Int): SettingsPageWithEntry? { + return pageWithEntryMap[pageId] + } + + fun getAllEntries(): Collection { + return entryMap.values + } + + fun getEntry(entryId: Int): SettingsEntry? { + return entryMap[entryId] } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index d4d8ea4567410..c031fe8083461 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -82,7 +82,7 @@ internal class TogglePermissionAppInfoPageProvider( internal fun navigator(permissionType: String, app: ApplicationInfo) = navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}") - internal fun buildPageId(permissionType: String): SettingsPage { + internal fun buildPageData(permissionType: String): SettingsPage { return SettingsPage.create( PAGE_NAME, PAGE_PARAMETER, bundleOf(PERMISSION to permissionType)) } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index f91a34a131c5e..de8a65e9aec5c 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -56,7 +56,7 @@ internal class TogglePermissionAppListPageProvider( override fun buildEntry(arguments: Bundle?): List { val permissionType = parameter.getStringArg(PERMISSION, arguments)!! val appListPage = SettingsPage.create(name, parameter, arguments) - val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageId(permissionType) + val appInfoPage = TogglePermissionAppInfoPageProvider.buildPageData(permissionType) val entryList = mutableListOf() // TODO: add more categories, such as personal, work, cloned, etc. for (category in listOf("personal")) {