Add entry provider in SPA framework, which is going to provide entry data for search & hierarchy generation.
Bug: 244122804
Test: manual - build Spa gallery
Change-Id: I57bb80e0749ca62c70abb09510ae8c9b95007c56
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index e34fedd..e583138 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -35,5 +35,13 @@
android:name=".GalleryDebugActivity"
android:exported="true">
</activity>
+
+ <provider
+ android:name=".GalleryEntryProvider"
+ android:authorities="com.android.spa.gallery.provider"
+ android:enabled="true"
+ android:exported="false">
+ </provider>
+
</application>
</manifest>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
new file mode 100644
index 0000000..3210eb5
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.EntryProvider
+
+class GalleryEntryProvider : EntryProvider(
+ SpaEnvironment.EntryRepository,
+ "com.android.settingslib.spa.gallery/.MainActivity",
+)
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 bd5aaa7..8aef2c6 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
@@ -17,6 +17,7 @@
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
@@ -32,6 +33,7 @@
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.EntryProvider.Companion.PAGE_INFO_QUERY
import com.android.settingslib.spa.framework.common.SettingsEntry
import com.android.settingslib.spa.framework.common.SettingsEntryRepository
import com.android.settingslib.spa.framework.common.SettingsPage
@@ -55,21 +57,12 @@
open class DebugActivity(
private val entryRepository: SettingsEntryRepository,
private val browseActivityClass: Class<*>,
+ private val entryProviderAuthorities: String? = null,
) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
-
- val packageName = browseActivityClass.packageName
- val className = browseActivityClass.toString().removePrefix("class $packageName")
- for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
- if (pageWithEntry.page.hasRuntimeParam()) continue
- val route = pageWithEntry.page.buildRoute()
- Log.d(
- "DEBUG ACTIVITY",
- "adb shell am start -n $packageName/$className -e $KEY_DESTINATION $route"
- )
- }
+ displayDebugMessage()
setContent {
SettingsTheme {
@@ -78,6 +71,30 @@
}
}
+ private fun displayDebugMessage() {
+ if (entryProviderAuthorities == null) return
+
+ try {
+ contentResolver.query(
+ Uri.parse("content://$entryProviderAuthorities/${PAGE_INFO_QUERY.queryPath}"),
+ null, null, null
+ ).use { cursor ->
+ while (cursor != null && cursor.moveToNext()) {
+ val route = cursor.getString(PAGE_INFO_QUERY.getIndex(ColumnName.PAGE_ROUTE))
+ val entryCount = cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.ENTRY_COUNT))
+ val hasRuntimeParam =
+ cursor.getInt(PAGE_INFO_QUERY.getIndex(ColumnName.HAS_RUNTIME_PARAM)) == 1
+ Log.d(
+ "DEBUG ACTIVITY", "Page Info: $route ($entryCount) " +
+ (if (hasRuntimeParam) "with" else "no") + "-runtime-params"
+ )
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("DEBUG ACTIVITY", "Provider querying exception:", e)
+ }
+ }
+
@Composable
private fun MainContent() {
val navController = rememberNavController()
@@ -122,7 +139,7 @@
for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
Preference(object : PreferenceModel {
override val title =
- "${pageWithEntry.page.displayName} (${pageWithEntry.entries.size})"
+ "${pageWithEntry.page.name} (${pageWithEntry.entries.size})"
override val summary = pageWithEntry.page.formatArguments().toState()
override val onClick =
navigator(route = ROUTE_PAGE + "/${pageWithEntry.page.id}")
@@ -142,7 +159,7 @@
fun OnePage(arguments: Bundle?) {
val id = arguments!!.getInt(PARAM_NAME_PAGE_ID)
val pageWithEntry = entryRepository.getPageWithEntry(id)!!
- RegularScaffold(title = "Page ${pageWithEntry.page.displayName}") {
+ RegularScaffold(title = "Page ${pageWithEntry.page.name}") {
Text(text = pageWithEntry.page.formatArguments())
Text(text = "Entry size: ${pageWithEntry.entries.size}")
Preference(model = object : PreferenceModel {
@@ -158,7 +175,7 @@
fun OneEntry(arguments: Bundle?) {
val id = arguments!!.getInt(PARAM_NAME_ENTRY_ID)
val entry = entryRepository.getEntry(id)!!
- RegularScaffold(title = "Entry ${entry.displayName}") {
+ RegularScaffold(title = "Entry ${entry.displayName()}") {
Preference(model = object : PreferenceModel {
override val title = "open entry"
override val enabled = (!entry.hasRuntimeParam()).toState()
@@ -172,9 +189,9 @@
private fun EntryList(entries: Collection<SettingsEntry>) {
for (entry in entries) {
Preference(object : PreferenceModel {
- override val title = entry.displayName
+ override val title = entry.displayName()
override val summary =
- "${entry.fromPage?.displayName} -> ${entry.toPage?.displayName}".toState()
+ "${entry.fromPage?.name} -> ${entry.toPage?.name}".toState()
override val onClick = navigator(route = ROUTE_ENTRY + "/${entry.id}")
})
}
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
new file mode 100644
index 0000000..90ce182
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+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.SettingsEntryRepository
+
+/**
+ * Enum to define all column names in provider.
+ */
+enum class ColumnName(val id: String) {
+ PAGE_NAME("pageName"),
+ PAGE_ROUTE("pageRoute"),
+ ENTRY_COUNT("entryCount"),
+ HAS_RUNTIME_PARAM("hasRuntimeParam"),
+ PAGE_START_ADB("pageStartAdb"),
+}
+
+data class QueryDefinition(
+ val queryPath: String,
+ val queryMatchCode: Int,
+ val columnNames: List<ColumnName>,
+) {
+ fun getColumns(): Array<String> {
+ return columnNames.map { it.id }.toTypedArray()
+ }
+
+ fun getIndex(name: ColumnName): Int {
+ return columnNames.indexOf(name)
+ }
+}
+
+open class EntryProvider(
+ private val entryRepository: SettingsEntryRepository,
+ private val browseActivityComponentName: String? = null,
+) : ContentProvider() {
+
+ private var mMatcher: UriMatcher? = null
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): 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<String>?
+ ): Int {
+ TODO("Implement this to handle requests to update one or more rows.")
+ }
+
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun attachInfo(context: Context?, info: ProviderInfo?) {
+ mMatcher = UriMatcher(UriMatcher.NO_MATCH)
+ if (info != null) {
+ mMatcher!!.addURI(
+ info.authority,
+ PAGE_START_COMMAND_QUERY.queryPath,
+ PAGE_START_COMMAND_QUERY.queryMatchCode
+ )
+ mMatcher!!.addURI(
+ info.authority,
+ PAGE_INFO_QUERY.queryPath,
+ PAGE_INFO_QUERY.queryMatchCode
+ )
+ }
+ super.attachInfo(context, info)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? {
+ return try {
+ when (mMatcher!!.match(uri)) {
+ PAGE_START_COMMAND_QUERY.queryMatchCode -> queryPageStartCommand()
+ PAGE_INFO_QUERY.queryMatchCode -> queryPageInfo()
+ else -> throw UnsupportedOperationException("Unknown Uri $uri")
+ }
+ } catch (e: UnsupportedOperationException) {
+ throw e
+ } catch (e: Exception) {
+ Log.e("EntryProvider", "Provider querying exception:", e)
+ null
+ }
+ }
+
+ private fun queryPageStartCommand(): Cursor {
+ val componentName = browseActivityComponentName ?: "[component-name]"
+ val cursor = MatrixCursor(PAGE_START_COMMAND_QUERY.getColumns())
+ for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+ val page = pageWithEntry.page
+ if (!page.hasRuntimeParam()) {
+ cursor.newRow().add(
+ ColumnName.PAGE_START_ADB.id,
+ "adb shell am start -n $componentName" +
+ " -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
+ )
+ }
+ }
+ return cursor
+ }
+
+ private fun queryPageInfo(): Cursor {
+ val cursor = MatrixCursor(PAGE_INFO_QUERY.getColumns())
+ for (pageWithEntry in entryRepository.getAllPageWithEntry()) {
+ val page = pageWithEntry.page
+ cursor.newRow().add(ColumnName.PAGE_NAME.id, page.name)
+ .add(ColumnName.PAGE_ROUTE.id, page.buildRoute())
+ .add(ColumnName.ENTRY_COUNT.id, pageWithEntry.entries.size)
+ .add(ColumnName.HAS_RUNTIME_PARAM.id, if (page.hasRuntimeParam()) 1 else 0)
+ }
+ return cursor
+ }
+
+ companion object {
+ val PAGE_START_COMMAND_QUERY = QueryDefinition(
+ "page_start", 1,
+ listOf(ColumnName.PAGE_START_ADB)
+ )
+
+ val PAGE_INFO_QUERY = QueryDefinition(
+ "page_info", 2,
+ listOf(
+ ColumnName.PAGE_NAME,
+ ColumnName.PAGE_ROUTE,
+ ColumnName.ENTRY_COUNT,
+ ColumnName.HAS_RUNTIME_PARAM,
+ )
+ )
+ }
+}
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 b0a1cbe..445c4eb 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
@@ -47,10 +47,6 @@
// 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,
-
// Defined parameters of this page.
val parameter: List<NamedNavArgument> = emptyList(),
@@ -74,7 +70,7 @@
}
fun formatAll(): String {
- return "$displayName ${formatArguments()}"
+ return "$name ${formatArguments()}"
}
fun buildRoute(highlightEntryName: String? = null): String {
@@ -104,10 +100,6 @@
// 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,
@@ -146,7 +138,7 @@
val uiLayoutImpl: (@Composable (arguments: Bundle?) -> Unit) = {},
) {
fun formatAll(): String {
- val content = listOf<String>(
+ val content = listOf(
"owner = ${owner.formatAll()}",
"linkFrom = ${fromPage?.formatAll()}",
"linkTo = ${toPage?.formatAll()}",
@@ -154,6 +146,11 @@
return content.joinToString("\n")
}
+ // The display name of the entry, for better readability.
+ fun displayName(): String {
+ return "${owner.name}:$name"
+ }
+
private fun getDisplayPage(): SettingsPage {
// Display the entry on its from-page, or on its owner page if the from-page is unset.
return fromPage ?: owner
@@ -185,7 +182,6 @@
private val name: String,
private val parameter: List<NamedNavArgument> = emptyList()
) {
- private var displayName: String? = null
private var arguments: Bundle? = null
fun build(): SettingsPage {
@@ -193,7 +189,6 @@
return SettingsPage(
id = "$name:${normArguments?.toString()}".toUniqueId(),
name = name,
- displayName = displayName ?: name,
parameter = parameter,
arguments = arguments,
)
@@ -209,7 +204,6 @@
* The helper to build a Settings Entry instance.
*/
class SettingsEntryBuilder(private val name: String, private val owner: SettingsPage) {
- private var displayName: String? = null
private var fromPage: SettingsPage? = null
private var toPage: SettingsPage? = null
private var isAllowSearch: Boolean? = null
@@ -220,7 +214,6 @@
fun build(): SettingsEntry {
return SettingsEntry(
id = "$name:${owner.id}(${fromPage?.id}-${toPage?.id})".toUniqueId(),
- displayName = displayName ?: "${owner.displayName}:$name",
name = name,
owner = owner,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index aaf8107..d7d7750 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -25,9 +25,12 @@
}
fun List<NamedNavArgument>.navLink(arguments: Bundle? = null): String {
- if (arguments == null) return ""
val argsArray = mutableListOf<String>()
for (navArg in this) {
+ if (arguments == null || !arguments.containsKey(navArg.name)) {
+ argsArray.add("[rt]")
+ continue
+ }
when (navArg.argument.type) {
NavType.StringType -> {
argsArray.add(arguments.getString(navArg.name, ""))