From 285690dcc31b7fdbbb8b4012a4f8cd21ee63e91c Mon Sep 17 00:00:00 2001 From: Zekan Qian Date: Wed, 19 Oct 2022 15:24:15 +0800 Subject: Move debug related activity & provider to debug folder. Add debug related activity & provider in Gallery. Bug: 244122804 Test: manual - build gallery Change-Id: Ie1b005b26eca5b7dd6472ebf6841531c2bc7a071 --- .../SettingsLib/Spa/gallery/AndroidManifest.xml | 18 +- .../spa/gallery/GalleryDebugActivity.kt | 21 -- .../settingslib/spa/framework/DebugActivity.kt | 269 --------------------- .../settingslib/spa/framework/EntryProvider.kt | 238 +----------------- .../spa/framework/common/ProviderColumn.kt | 126 ++++++++++ .../spa/framework/common/SettingsPage.kt | 40 +++ .../spa/framework/debug/DebugActivity.kt | 228 +++++++++++++++++ .../spa/framework/debug/DebugProvider.kt | 176 ++++++++++++++ 8 files changed, 596 insertions(+), 520 deletions(-) delete mode 100644 packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt delete mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt create mode 100644 packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml index 2ed8bca1e5d8..f1a24aff4319 100644 --- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml +++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml @@ -34,14 +34,24 @@ + + + + + - 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 deleted file mode 100644 index 23072a231417..000000000000 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt +++ /dev/null @@ -1,21 +0,0 @@ -/* - * 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() 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 deleted file mode 100644 index 6f968180e243..000000000000 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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.content.Intent -import android.net.Uri -import android.os.Bundle -import android.util.Log -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.compose.runtime.remember -import androidx.compose.ui.platform.LocalContext -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.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 -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 TAG = "DebugActivity" -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" - -/** - * The Debug Activity to display all Spa Pages & Entries. - * One can open the debug activity by: - * $ adb shell am start -n - * For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity - * For SettingsGoogle, Activity = com.android.settings/.spa.SpaDebugActivity - */ -open class DebugActivity : ComponentActivity() { - private val spaEnvironment get() = SpaEnvironmentFactory.instance - - override fun onCreate(savedInstanceState: Bundle?) { - setTheme(R.style.Theme_SpaLib_DayNight) - super.onCreate(savedInstanceState) - spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) - - setContent { - SettingsTheme { - MainContent() - } - } - } - - private fun displayDebugMessage() { - val entryProviderAuthorities = spaEnvironment.entryProviderAuthorities ?: return - - try { - val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY - contentResolver.query( - Uri.parse("content://$entryProviderAuthorities/${query.queryPath}"), - null, null, null - ).use { cursor -> - while (cursor != null && cursor.moveToNext()) { - val route = cursor.getString(query, EntryProvider.ColumnEnum.PAGE_ROUTE) - val entryCount = cursor.getInt(query, EntryProvider.ColumnEnum.PAGE_ENTRY_COUNT) - val hasRuntimeParam = - cursor.getBoolean(query, EntryProvider.ColumnEnum.HAS_RUNTIME_PARAM) - val message = "Page Info: $route ($entryCount) " + - (if (hasRuntimeParam) "with" else "no") + "-runtime-params" - spaEnvironment.logger.message(TAG, message, category = LogCategory.FRAMEWORK) - } - } - } catch (e: Exception) { - Log.e(TAG, "Provider querying exception:", e) - } - } - - @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.StringType }, - ) - ) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) } - composable( - route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}", - arguments = listOf( - navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.StringType }, - ) - ) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) } - } - } - } - - @Composable - fun RootPage() { - val entryRepository by spaEnvironment.entryRepository - val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() } - val allEntry = remember { entryRepository.getAllEntries() } - HomeScaffold(title = "Settings Debug") { - Preference(object : PreferenceModel { - override val title = "List All Pages (${allPageWithEntry.size})" - override val onClick = navigator(route = ROUTE_All_PAGES) - }) - Preference(object : PreferenceModel { - override val title = "List All Entries (${allEntry.size})" - override val onClick = navigator(route = ROUTE_All_ENTRIES) - }) - Preference(object : PreferenceModel { - override val title = "Query EntryProvider" - override val enabled = isEntryProviderAvailable().toState() - override val onClick = { displayDebugMessage() } - }) - } - } - - @Composable - fun AllPages() { - val entryRepository by spaEnvironment.entryRepository - val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() } - RegularScaffold(title = "All Pages (${allPageWithEntry.size})") { - for (pageWithEntry in allPageWithEntry) { - 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() { - val entryRepository by spaEnvironment.entryRepository - val allEntry = remember { entryRepository.getAllEntries() } - RegularScaffold(title = "All Entries (${allEntry.size})") { - EntryList(allEntry) - } - } - - @Composable - fun OnePage(arguments: Bundle?) { - val entryRepository by spaEnvironment.entryRepository - val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "") - val pageWithEntry = entryRepository.getPageWithEntry(id)!! - RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") { - Text(text = "id = ${pageWithEntry.page.id}") - Text(text = pageWithEntry.page.formatArguments()) - Text(text = "Entry size: ${pageWithEntry.entries.size}") - Preference(model = object : PreferenceModel { - override val title = "open page" - override val enabled = isPageClickable(pageWithEntry.page).toState() - override val onClick = openPage(pageWithEntry.page) - }) - EntryList(pageWithEntry.entries) - } - } - - @Composable - fun OneEntry(arguments: Bundle?) { - val entryRepository by spaEnvironment.entryRepository - val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "") - val entry = entryRepository.getEntry(id)!! - val entryContent = remember { entry.formatContent() } - RegularScaffold(title = "Entry - ${entry.displayTitle()}") { - Preference(model = object : PreferenceModel { - override val title = "open entry" - override val enabled = isEntryClickable(entry).toState() - override val onClick = openEntry(entry) - }) - Text(text = entryContent) - } - } - - @Composable - private fun EntryList(entries: Collection) { - for (entry in entries) { - Preference(object : PreferenceModel { - override val title = entry.displayTitle() - override val summary = - "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState() - override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}") - }) - } - } - - @Composable - private fun openPage(page: SettingsPage): (() -> Unit)? { - if (!isPageClickable(page)) return null - val context = LocalContext.current - val route = page.buildRoute() - val intent = Intent(context, spaEnvironment.browseActivityClass).apply { - putExtra(KEY_DESTINATION, route) - } - return { - spaEnvironment.logger.message( - TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK - ) - context.startActivity(intent) - } - } - - @Composable - private fun openEntry(entry: SettingsEntry): (() -> Unit)? { - if (!isEntryClickable(entry)) return null - val context = LocalContext.current - val route = entry.containerPage().buildRoute() - val intent = Intent(context, spaEnvironment.browseActivityClass).apply { - putExtra(KEY_DESTINATION, route) - putExtra(KEY_HIGHLIGHT_ENTRY, entry.id) - } - return { - spaEnvironment.logger.message( - TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK - ) - context.startActivity(intent) - } - } - - private fun isEntryProviderAvailable(): Boolean { - return spaEnvironment.entryProviderAuthorities != null - } - - private fun isPageClickable(page: SettingsPage): Boolean { - return spaEnvironment.browseActivityClass != null && !page.hasRuntimeParam() - } - - private fun isEntryClickable(entry: SettingsEntry): Boolean { - return spaEnvironment.browseActivityClass != null && - !entry.containerPage().hasRuntimeParam() - } -} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt index 532f63b67c5d..d6317085e4f9 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt @@ -16,21 +16,22 @@ package com.android.settingslib.spa.framework -import android.content.ComponentName import android.content.ContentProvider import android.content.ContentValues import android.content.Context import android.content.Intent -import android.content.Intent.URI_INTENT_SCHEME import android.content.UriMatcher import android.content.pm.ProviderInfo import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import android.util.Log +import com.android.settingslib.spa.framework.common.ColumnEnum +import com.android.settingslib.spa.framework.common.QueryEnum import com.android.settingslib.spa.framework.common.SettingsEntry -import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.addUri +import com.android.settingslib.spa.framework.common.getColumns private const val TAG = "EntryProvider" @@ -39,117 +40,15 @@ private const val TAG = "EntryProvider" * One can query the provider result by: * $ adb shell content query --uri content:/// * For gallery, AuthorityPath = com.android.spa.gallery.provider - * For SettingsGoogle, AuthorityPath = com.android.settings.spa.provider + * For Settings, AuthorityPath = com.android.settings.spa.provider * Some examples: - * $ adb shell content query --uri content:///page_debug - * $ adb shell content query --uri content:///entry_debug - * $ adb shell content query --uri content:///page_info - * $ adb shell content query --uri content:///entry_info * $ adb shell content query --uri content:///search_sitemap * $ adb shell content query --uri content:///search_static * $ adb shell content query --uri content:///search_dynamic */ open class EntryProvider : ContentProvider() { private val spaEnvironment get() = SpaEnvironmentFactory.instance - - /** - * Enum to define all column names in provider. - */ - enum class ColumnEnum(val id: String) { - // Columns related to page - PAGE_ID("pageId"), - PAGE_NAME("pageName"), - PAGE_ROUTE("pageRoute"), - PAGE_INTENT_URI("pageIntent"), - PAGE_ENTRY_COUNT("entryCount"), - HAS_RUNTIME_PARAM("hasRuntimeParam"), - PAGE_START_ADB("pageStartAdb"), - - // Columns related to entry - ENTRY_ID("entryId"), - ENTRY_NAME("entryName"), - ENTRY_ROUTE("entryRoute"), - ENTRY_INTENT_URI("entryIntent"), - ENTRY_HIERARCHY_PATH("entryPath"), - ENTRY_START_ADB("entryStartAdb"), - - // Columns related to search - ENTRY_TITLE("entryTitle"), - ENTRY_SEARCH_KEYWORD("entrySearchKw"), - } - - /** - * Enum to define all queries supported in the provider. - */ - enum class QueryEnum( - val queryPath: String, - val queryMatchCode: Int, - val columnNames: List - ) { - // For debug - PAGE_DEBUG_QUERY( - "page_debug", 1, - listOf(ColumnEnum.PAGE_START_ADB) - ), - ENTRY_DEBUG_QUERY( - "entry_debug", 2, - listOf(ColumnEnum.ENTRY_START_ADB) - ), - - // page related queries. - PAGE_INFO_QUERY( - "page_info", 100, - listOf( - ColumnEnum.PAGE_ID, - ColumnEnum.PAGE_NAME, - ColumnEnum.PAGE_ROUTE, - ColumnEnum.PAGE_INTENT_URI, - ColumnEnum.PAGE_ENTRY_COUNT, - ColumnEnum.HAS_RUNTIME_PARAM, - ) - ), - - // entry related queries - ENTRY_INFO_QUERY( - "entry_info", 200, - listOf( - ColumnEnum.ENTRY_ID, - ColumnEnum.ENTRY_NAME, - ColumnEnum.ENTRY_ROUTE, - ColumnEnum.ENTRY_INTENT_URI, - ) - ), - - // Search related queries - SEARCH_SITEMAP_QUERY( - "search_sitemap", 300, - listOf( - ColumnEnum.ENTRY_ID, - ColumnEnum.ENTRY_HIERARCHY_PATH, - ) - ), - SEARCH_STATIC_DATA_QUERY( - "search_static", 301, - listOf( - ColumnEnum.ENTRY_ID, - ColumnEnum.ENTRY_TITLE, - ColumnEnum.ENTRY_SEARCH_KEYWORD, - ) - ), - SEARCH_DYNAMIC_DATA_QUERY( - "search_dynamic", 302, - listOf( - ColumnEnum.ENTRY_ID, - ColumnEnum.ENTRY_TITLE, - ColumnEnum.ENTRY_SEARCH_KEYWORD, - ) - ), - } - private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) - private fun addUri(authority: String, query: QueryEnum) { - uriMatcher.addURI(authority, query.queryPath, query.queryMatchCode) - } override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { TODO("Implement this to handle requests to delete one or more rows") @@ -182,13 +81,9 @@ open class EntryProvider : ContentProvider() { override fun attachInfo(context: Context?, info: ProviderInfo?) { if (info != null) { - addUri(info.authority, QueryEnum.PAGE_DEBUG_QUERY) - addUri(info.authority, QueryEnum.ENTRY_DEBUG_QUERY) - addUri(info.authority, QueryEnum.PAGE_INFO_QUERY) - addUri(info.authority, QueryEnum.ENTRY_INFO_QUERY) - addUri(info.authority, QueryEnum.SEARCH_SITEMAP_QUERY) - addUri(info.authority, QueryEnum.SEARCH_STATIC_DATA_QUERY) - addUri(info.authority, QueryEnum.SEARCH_DYNAMIC_DATA_QUERY) + QueryEnum.SEARCH_SITEMAP_QUERY.addUri(uriMatcher, info.authority) + QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority) + QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority) } super.attachInfo(context, info) } @@ -202,10 +97,6 @@ open class EntryProvider : ContentProvider() { ): Cursor? { return try { when (uriMatcher.match(uri)) { - QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug() - QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug() - QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo() - QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo() QueryEnum.SEARCH_SITEMAP_QUERY.queryMatchCode -> querySearchSitemap() QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData() QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData() @@ -219,73 +110,18 @@ open class EntryProvider : ContentProvider() { } } - private fun queryPageDebug(): Cursor { - val entryRepository by spaEnvironment.entryRepository - val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns()) - for (pageWithEntry in entryRepository.getAllPageWithEntry()) { - val command = createBrowsePageAdbCommand(pageWithEntry.page) - if (command != null) { - cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command) - } - } - return cursor - } - - private fun queryEntryDebug(): Cursor { - val entryRepository by spaEnvironment.entryRepository - val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns()) - for (entry in entryRepository.getAllEntries()) { - val command = createBrowsePageAdbCommand(entry.containerPage(), entry.id) - if (command != null) { - cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command) - } - } - return cursor - } - - private fun queryPageInfo(): Cursor { - val entryRepository by spaEnvironment.entryRepository - val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns()) - for (pageWithEntry in entryRepository.getAllPageWithEntry()) { - val page = pageWithEntry.page - cursor.newRow() - .add(ColumnEnum.PAGE_ID.id, page.id) - .add(ColumnEnum.PAGE_NAME.id, page.displayName) - .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute()) - .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size) - .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0) - .add( - ColumnEnum.PAGE_INTENT_URI.id, - createBrowsePageIntent(page).toUri(URI_INTENT_SCHEME) - ) - } - return cursor - } - - private fun queryEntryInfo(): Cursor { - val entryRepository by spaEnvironment.entryRepository - val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns()) - for (entry in entryRepository.getAllEntries()) { - cursor.newRow() - .add(ColumnEnum.ENTRY_ID.id, entry.id) - .add(ColumnEnum.ENTRY_NAME.id, entry.displayName) - .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute()) - .add( - ColumnEnum.ENTRY_INTENT_URI.id, - createBrowsePageIntent(entry.containerPage(), entry.id).toUri(URI_INTENT_SCHEME) - ) - } - return cursor - } - private fun querySearchSitemap(): Cursor { val entryRepository by spaEnvironment.entryRepository val cursor = MatrixCursor(QueryEnum.SEARCH_SITEMAP_QUERY.getColumns()) for (entry in entryRepository.getAllEntries()) { if (!entry.isAllowSearch) continue + val intent = entry.containerPage() + .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) + ?: Intent() cursor.newRow() .add(ColumnEnum.ENTRY_ID.id, entry.id) .add(ColumnEnum.ENTRY_HIERARCHY_PATH.id, entryRepository.getEntryPath(entry.id)) + .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME)) } return cursor } @@ -321,54 +157,4 @@ open class EntryProvider : ContentProvider() { searchData?.keyword ?: emptyList() ) } - - private fun createBrowsePageIntent(page: SettingsPage, entryId: String? = null): Intent { - if (!isPageBrowsable(page)) return Intent() - return Intent().setComponent(ComponentName(context!!, spaEnvironment.browseActivityClass!!)) - .apply { - putExtra(BrowseActivity.KEY_DESTINATION, page.buildRoute()) - if (entryId != null) { - putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId) - } - } - } - - private fun createBrowsePageAdbCommand(page: SettingsPage, entryId: String? = null): String? { - if (!isPageBrowsable(page)) return null - val packageName = context!!.packageName - val activityName = spaEnvironment.browseActivityClass!!.name.replace(packageName, "") - val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}" - val highlightParam = - if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else "" - return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam" - } - - private fun isPageBrowsable(page: SettingsPage): Boolean { - return context != null && - spaEnvironment.browseActivityClass != null && - !page.hasRuntimeParam() - } -} - -fun EntryProvider.QueryEnum.getColumns(): Array { - return columnNames.map { it.id }.toTypedArray() -} - -fun EntryProvider.QueryEnum.getIndex(name: EntryProvider.ColumnEnum): Int { - return columnNames.indexOf(name) -} - -fun Cursor.getString(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): String { - return this.getString(query.getIndex(columnName)) -} - -fun Cursor.getInt(query: EntryProvider.QueryEnum, columnName: EntryProvider.ColumnEnum): Int { - return this.getInt(query.getIndex(columnName)) -} - -fun Cursor.getBoolean( - query: EntryProvider.QueryEnum, - columnName: EntryProvider.ColumnEnum -): Boolean { - return this.getInt(query.getIndex(columnName)) == 1 } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt new file mode 100644 index 000000000000..0707429505c8 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt @@ -0,0 +1,126 @@ +/* + * 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.content.UriMatcher + +/** + * Enum to define all column names in provider. + */ +enum class ColumnEnum(val id: String) { + // Columns related to page + PAGE_ID("pageId"), + PAGE_NAME("pageName"), + PAGE_ROUTE("pageRoute"), + PAGE_INTENT_URI("pageIntent"), + PAGE_ENTRY_COUNT("entryCount"), + HAS_RUNTIME_PARAM("hasRuntimeParam"), + PAGE_START_ADB("pageStartAdb"), + + // Columns related to entry + ENTRY_ID("entryId"), + ENTRY_NAME("entryName"), + ENTRY_ROUTE("entryRoute"), + ENTRY_INTENT_URI("entryIntent"), + ENTRY_HIERARCHY_PATH("entryPath"), + ENTRY_START_ADB("entryStartAdb"), + + // Columns related to search + ENTRY_TITLE("entryTitle"), + ENTRY_SEARCH_KEYWORD("entrySearchKw"), +} + +/** + * Enum to define all queries supported in the provider. + */ +enum class QueryEnum( + val queryPath: String, + val queryMatchCode: Int, + val columnNames: List +) { + // For debug + PAGE_DEBUG_QUERY( + "page_debug", 1, + listOf(ColumnEnum.PAGE_START_ADB) + ), + ENTRY_DEBUG_QUERY( + "entry_debug", 2, + listOf(ColumnEnum.ENTRY_START_ADB) + ), + + // page related queries. + PAGE_INFO_QUERY( + "page_info", 100, + listOf( + ColumnEnum.PAGE_ID, + ColumnEnum.PAGE_NAME, + ColumnEnum.PAGE_ROUTE, + ColumnEnum.PAGE_INTENT_URI, + ColumnEnum.PAGE_ENTRY_COUNT, + ColumnEnum.HAS_RUNTIME_PARAM, + ) + ), + + // entry related queries + ENTRY_INFO_QUERY( + "entry_info", 200, + listOf( + ColumnEnum.ENTRY_ID, + ColumnEnum.ENTRY_NAME, + ColumnEnum.ENTRY_ROUTE, + ColumnEnum.ENTRY_INTENT_URI, + ) + ), + + // Search related queries + SEARCH_SITEMAP_QUERY( + "search_sitemap", 300, + listOf( + ColumnEnum.ENTRY_ID, + ColumnEnum.ENTRY_HIERARCHY_PATH, + ColumnEnum.ENTRY_INTENT_URI, + ) + ), + SEARCH_STATIC_DATA_QUERY( + "search_static", 301, + listOf( + ColumnEnum.ENTRY_ID, + ColumnEnum.ENTRY_TITLE, + ColumnEnum.ENTRY_SEARCH_KEYWORD, + ) + ), + SEARCH_DYNAMIC_DATA_QUERY( + "search_dynamic", 302, + listOf( + ColumnEnum.ENTRY_ID, + ColumnEnum.ENTRY_TITLE, + ColumnEnum.ENTRY_SEARCH_KEYWORD, + ) + ), +} + +internal fun QueryEnum.getColumns(): Array { + return columnNames.map { it.id }.toTypedArray() +} + +internal fun QueryEnum.getIndex(name: ColumnEnum): Int { + return columnNames.indexOf(name) +} + +internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) { + uriMatcher.addURI(authority, queryPath, queryMatchCode) +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt index 8f63c47b1a9b..07df96e778c4 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt @@ -16,8 +16,13 @@ package com.android.settingslib.spa.framework.common +import android.app.Activity +import android.content.ComponentName +import android.content.Context +import android.content.Intent import android.os.Bundle import androidx.navigation.NamedNavArgument +import com.android.settingslib.spa.framework.BrowseActivity import com.android.settingslib.spa.framework.util.isRuntimeParam import com.android.settingslib.spa.framework.util.navLink import com.android.settingslib.spa.framework.util.normalize @@ -111,6 +116,41 @@ data class SettingsPage( details = formatDisplayTitle() ) } + + fun createBrowseIntent( + context: Context?, + browseActivityClass: Class?, + entryId: String? = null + ): Intent? { + if (!isBrowsable(context, browseActivityClass)) return null + return Intent().setComponent(ComponentName(context!!, browseActivityClass!!)) + .apply { + putExtra(BrowseActivity.KEY_DESTINATION, buildRoute()) + if (entryId != null) { + putExtra(BrowseActivity.KEY_HIGHLIGHT_ENTRY, entryId) + } + } + } + + fun createBrowseAdbCommand( + context: Context?, + browseActivityClass: Class?, + entryId: String? = null + ): String? { + if (!isBrowsable(context, browseActivityClass)) return null + val packageName = context!!.packageName + val activityName = browseActivityClass!!.name.replace(packageName, "") + val destinationParam = " -e ${BrowseActivity.KEY_DESTINATION} ${buildRoute()}" + val highlightParam = + if (entryId != null) " -e ${BrowseActivity.KEY_HIGHLIGHT_ENTRY} $entryId" else "" + return "adb shell am start -n $packageName/$activityName$destinationParam$highlightParam" + } + + fun isBrowsable(context: Context?, browseActivityClass: Class?): Boolean { + return context != null && + browseActivityClass != null && + !hasRuntimeParam() + } } fun String.toHashId(): String { diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt new file mode 100644 index 000000000000..301508074f30 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt @@ -0,0 +1,228 @@ +/* + * 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.debug + +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.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext +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.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 +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 TAG = "DebugActivity" +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" + +/** + * The Debug Activity to display all Spa Pages & Entries. + * One can open the debug activity by: + * $ adb shell am start -n /com.android.settingslib.spa.framework.debug.DebugActivity + * For gallery, Package = com.android.settingslib.spa.gallery + */ +class DebugActivity : ComponentActivity() { + private val spaEnvironment get() = SpaEnvironmentFactory.instance + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(R.style.Theme_SpaLib_DayNight) + super.onCreate(savedInstanceState) + spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK) + + 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.StringType }, + ) + ) { navBackStackEntry -> OnePage(navBackStackEntry.arguments) } + composable( + route = "$ROUTE_ENTRY/{$PARAM_NAME_ENTRY_ID}", + arguments = listOf( + navArgument(PARAM_NAME_ENTRY_ID) { type = NavType.StringType }, + ) + ) { navBackStackEntry -> OneEntry(navBackStackEntry.arguments) } + } + } + } + + @Composable + fun RootPage() { + val entryRepository by spaEnvironment.entryRepository + val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() } + val allEntry = remember { entryRepository.getAllEntries() } + HomeScaffold(title = "Settings Debug") { + Preference(object : PreferenceModel { + override val title = "List All Pages (${allPageWithEntry.size})" + override val onClick = navigator(route = ROUTE_All_PAGES) + }) + Preference(object : PreferenceModel { + override val title = "List All Entries (${allEntry.size})" + override val onClick = navigator(route = ROUTE_All_ENTRIES) + }) + } + } + + @Composable + fun AllPages() { + val entryRepository by spaEnvironment.entryRepository + val allPageWithEntry = remember { entryRepository.getAllPageWithEntry() } + RegularScaffold(title = "All Pages (${allPageWithEntry.size})") { + for (pageWithEntry in allPageWithEntry) { + 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() { + val entryRepository by spaEnvironment.entryRepository + val allEntry = remember { entryRepository.getAllEntries() } + RegularScaffold(title = "All Entries (${allEntry.size})") { + EntryList(allEntry) + } + } + + @Composable + fun OnePage(arguments: Bundle?) { + val context = LocalContext.current + val entryRepository by spaEnvironment.entryRepository + val id = arguments!!.getString(PARAM_NAME_PAGE_ID, "") + val pageWithEntry = entryRepository.getPageWithEntry(id)!! + RegularScaffold(title = "Page - ${pageWithEntry.page.displayName}") { + Text(text = "id = ${pageWithEntry.page.id}") + Text(text = pageWithEntry.page.formatArguments()) + Text(text = "Entry size: ${pageWithEntry.entries.size}") + Preference(model = object : PreferenceModel { + override val title = "open page" + override val enabled = + pageWithEntry.page.isBrowsable(context, spaEnvironment.browseActivityClass) + .toState() + override val onClick = openPage(pageWithEntry.page) + }) + EntryList(pageWithEntry.entries) + } + } + + @Composable + fun OneEntry(arguments: Bundle?) { + val context = LocalContext.current + val entryRepository by spaEnvironment.entryRepository + val id = arguments!!.getString(PARAM_NAME_ENTRY_ID, "") + val entry = entryRepository.getEntry(id)!! + val entryContent = remember { entry.formatContent() } + RegularScaffold(title = "Entry - ${entry.displayTitle()}") { + Preference(model = object : PreferenceModel { + override val title = "open entry" + override val enabled = + entry.containerPage().isBrowsable(context, spaEnvironment.browseActivityClass) + .toState() + override val onClick = openEntry(entry) + }) + Text(text = entryContent) + } + } + + @Composable + private fun EntryList(entries: Collection) { + for (entry in entries) { + Preference(object : PreferenceModel { + override val title = entry.displayTitle() + override val summary = + "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState() + override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}") + }) + } + } + + @Composable + private fun openPage(page: SettingsPage): (() -> Unit)? { + val context = LocalContext.current + val intent = + page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: return null + val route = page.buildRoute() + return { + spaEnvironment.logger.message( + TAG, "OpenPage: $route", category = LogCategory.FRAMEWORK + ) + context.startActivity(intent) + } + } + + @Composable + private fun openEntry(entry: SettingsEntry): (() -> Unit)? { + val context = LocalContext.current + val intent = entry.containerPage() + .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) + ?: return null + val route = entry.containerPage().buildRoute() + return { + spaEnvironment.logger.message( + TAG, "OpenEntry: $route", category = LogCategory.FRAMEWORK + ) + context.startActivity(intent) + } + } +} + +/** + * A blank activity without any page. + */ +class BlankActivity : ComponentActivity() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt new file mode 100644 index 000000000000..6c271094de9f --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugProvider.kt @@ -0,0 +1,176 @@ +/* + * 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.debug + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.content.Intent.URI_INTENT_SCHEME +import android.content.UriMatcher +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.util.Log +import com.android.settingslib.spa.framework.common.ColumnEnum +import com.android.settingslib.spa.framework.common.QueryEnum +import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.common.addUri +import com.android.settingslib.spa.framework.common.getColumns + +private const val TAG = "DebugProvider" + +/** + * The content provider to return debug data. + * One can query the provider result by: + * $ adb shell content query --uri content:/// + * For gallery, AuthorityPath = com.android.spa.gallery.debug + * Some examples: + * $ adb shell content query --uri content:///page_debug + * $ adb shell content query --uri content:///entry_debug + * $ adb shell content query --uri content:///page_info + * $ adb shell content query --uri content:///entry_info + */ +class DebugProvider : ContentProvider() { + private val spaEnvironment get() = SpaEnvironmentFactory.instance + private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH) + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + TODO("Implement this to handle requests to delete one or more rows") + } + + override fun getType(uri: Uri): String? { + TODO( + "Implement this to handle requests for the MIME type of the data" + + "at the given URI" + ) + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + TODO("Implement this to handle requests to insert a new row.") + } + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array? + ): Int { + TODO("Implement this to handle requests to update one or more rows.") + } + + override fun onCreate(): Boolean { + Log.d(TAG, "onCreate") + return true + } + + override fun attachInfo(context: Context?, info: ProviderInfo?) { + if (info != null) { + QueryEnum.PAGE_DEBUG_QUERY.addUri(uriMatcher, info.authority) + QueryEnum.ENTRY_DEBUG_QUERY.addUri(uriMatcher, info.authority) + QueryEnum.PAGE_INFO_QUERY.addUri(uriMatcher, info.authority) + QueryEnum.ENTRY_INFO_QUERY.addUri(uriMatcher, info.authority) + } + super.attachInfo(context, info) + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? { + return try { + when (uriMatcher.match(uri)) { + QueryEnum.PAGE_DEBUG_QUERY.queryMatchCode -> queryPageDebug() + QueryEnum.ENTRY_DEBUG_QUERY.queryMatchCode -> queryEntryDebug() + QueryEnum.PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo() + QueryEnum.ENTRY_INFO_QUERY.queryMatchCode -> queryEntryInfo() + else -> throw UnsupportedOperationException("Unknown Uri $uri") + } + } catch (e: UnsupportedOperationException) { + throw e + } catch (e: Exception) { + Log.e(TAG, "Provider querying exception:", e) + null + } + } + + private fun queryPageDebug(): Cursor { + val entryRepository by spaEnvironment.entryRepository + val cursor = MatrixCursor(QueryEnum.PAGE_DEBUG_QUERY.getColumns()) + for (pageWithEntry in entryRepository.getAllPageWithEntry()) { + val command = pageWithEntry.page.createBrowseAdbCommand( + context, + spaEnvironment.browseActivityClass + ) + if (command != null) { + cursor.newRow().add(ColumnEnum.PAGE_START_ADB.id, command) + } + } + return cursor + } + + private fun queryEntryDebug(): Cursor { + val entryRepository by spaEnvironment.entryRepository + val cursor = MatrixCursor(QueryEnum.ENTRY_DEBUG_QUERY.getColumns()) + for (entry in entryRepository.getAllEntries()) { + val command = entry.containerPage() + .createBrowseAdbCommand(context, spaEnvironment.browseActivityClass, entry.id) + if (command != null) { + cursor.newRow().add(ColumnEnum.ENTRY_START_ADB.id, command) + } + } + return cursor + } + + private fun queryPageInfo(): Cursor { + val entryRepository by spaEnvironment.entryRepository + val cursor = MatrixCursor(QueryEnum.PAGE_INFO_QUERY.getColumns()) + for (pageWithEntry in entryRepository.getAllPageWithEntry()) { + val page = pageWithEntry.page + val intent = + page.createBrowseIntent(context, spaEnvironment.browseActivityClass) ?: Intent() + cursor.newRow() + .add(ColumnEnum.PAGE_ID.id, page.id) + .add(ColumnEnum.PAGE_NAME.id, page.displayName) + .add(ColumnEnum.PAGE_ROUTE.id, page.buildRoute()) + .add(ColumnEnum.PAGE_ENTRY_COUNT.id, pageWithEntry.entries.size) + .add(ColumnEnum.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0) + .add(ColumnEnum.PAGE_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME)) + } + return cursor + } + + private fun queryEntryInfo(): Cursor { + val entryRepository by spaEnvironment.entryRepository + val cursor = MatrixCursor(QueryEnum.ENTRY_INFO_QUERY.getColumns()) + for (entry in entryRepository.getAllEntries()) { + val intent = entry.containerPage() + .createBrowseIntent(context, spaEnvironment.browseActivityClass, entry.id) + ?: Intent() + cursor.newRow() + .add(ColumnEnum.ENTRY_ID.id, entry.id) + .add(ColumnEnum.ENTRY_NAME.id, entry.displayName) + .add(ColumnEnum.ENTRY_ROUTE.id, entry.containerPage().buildRoute()) + .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(URI_INTENT_SCHEME)) + } + return cursor + } +} -- cgit v1.2.3-59-g8ed1b