Update SpaSearchProvider with SpaSearchContract
SpaSearchContract will have an equivalent copy in service for consistency Bug: 253981143 Test: manual, unit test Change-Id: Iaed6f91268364abe0f46e92f610c0e182139c684
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.search
|
||||
|
||||
/**
|
||||
* Intent action used to identify SpaSearchProvider instances. This is used in the {@code
|
||||
* <intent-filter>} of a {@code <provider>}.
|
||||
*/
|
||||
const val PROVIDER_INTERFACE = "android.content.action.SPA_SEARCH_PROVIDER"
|
||||
|
||||
/** ContentProvider path for search static data */
|
||||
const val SEARCH_STATIC_DATA = "search_static_data"
|
||||
|
||||
/** ContentProvider path for search dynamic data */
|
||||
const val SEARCH_DYNAMIC_DATA = "search_dynamic_data"
|
||||
|
||||
/** ContentProvider path for search immutable status */
|
||||
const val SEARCH_IMMUTABLE_STATUS = "search_immutable_status"
|
||||
|
||||
/** ContentProvider path for search mutable status */
|
||||
const val SEARCH_MUTABLE_STATUS = "search_mutable_status"
|
||||
|
||||
/** Enum to define all column names in provider. */
|
||||
enum class ColumnEnum(val id: String) {
|
||||
ENTRY_ID("entryId"),
|
||||
SEARCH_TITLE("searchTitle"),
|
||||
SEARCH_KEYWORD("searchKw"),
|
||||
SEARCH_PATH("searchPath"),
|
||||
INTENT_TARGET_PACKAGE("intentTargetPackage"),
|
||||
INTENT_TARGET_CLASS("intentTargetClass"),
|
||||
INTENT_EXTRAS("intentExtras"),
|
||||
SLICE_URI("sliceUri"),
|
||||
LEGACY_KEY("legacyKey"),
|
||||
ENTRY_DISABLED("entryDisabled"),
|
||||
}
|
||||
|
||||
/** Enum to define all queries supported in the provider. */
|
||||
@SuppressWarnings("Immutable")
|
||||
enum class QueryEnum(
|
||||
val queryPath: String,
|
||||
val columnNames: List<ColumnEnum>
|
||||
) {
|
||||
SEARCH_STATIC_DATA_QUERY(
|
||||
SEARCH_STATIC_DATA,
|
||||
listOf(
|
||||
ColumnEnum.ENTRY_ID,
|
||||
ColumnEnum.SEARCH_TITLE,
|
||||
ColumnEnum.SEARCH_KEYWORD,
|
||||
ColumnEnum.SEARCH_PATH,
|
||||
ColumnEnum.INTENT_TARGET_PACKAGE,
|
||||
ColumnEnum.INTENT_TARGET_CLASS,
|
||||
ColumnEnum.INTENT_EXTRAS,
|
||||
ColumnEnum.SLICE_URI,
|
||||
ColumnEnum.LEGACY_KEY
|
||||
)
|
||||
),
|
||||
SEARCH_DYNAMIC_DATA_QUERY(
|
||||
SEARCH_DYNAMIC_DATA,
|
||||
listOf(
|
||||
ColumnEnum.ENTRY_ID,
|
||||
ColumnEnum.SEARCH_TITLE,
|
||||
ColumnEnum.SEARCH_KEYWORD,
|
||||
ColumnEnum.SEARCH_PATH,
|
||||
ColumnEnum.INTENT_TARGET_PACKAGE,
|
||||
ColumnEnum.INTENT_TARGET_CLASS,
|
||||
ColumnEnum.SLICE_URI,
|
||||
ColumnEnum.LEGACY_KEY
|
||||
)
|
||||
),
|
||||
SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
|
||||
SEARCH_IMMUTABLE_STATUS,
|
||||
listOf(
|
||||
ColumnEnum.ENTRY_ID,
|
||||
ColumnEnum.ENTRY_DISABLED,
|
||||
)
|
||||
),
|
||||
SEARCH_MUTABLE_STATUS_DATA_QUERY(
|
||||
SEARCH_MUTABLE_STATUS,
|
||||
listOf(
|
||||
ColumnEnum.ENTRY_ID,
|
||||
ColumnEnum.ENTRY_DISABLED,
|
||||
)
|
||||
),
|
||||
}
|
||||
@@ -19,22 +19,22 @@ package com.android.settingslib.spa.search
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.UriMatcher
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.settingslib.spa.framework.common.ColumnEnum
|
||||
import com.android.settingslib.spa.framework.common.QueryEnum
|
||||
import com.android.settingslib.spa.framework.common.EntryStatusData
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntry
|
||||
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
|
||||
import com.android.settingslib.spa.framework.common.addUri
|
||||
import com.android.settingslib.spa.framework.common.getColumns
|
||||
import com.android.settingslib.spa.framework.util.SESSION_SEARCH
|
||||
import com.android.settingslib.spa.framework.util.createIntent
|
||||
import com.android.settingslib.spa.slice.fromEntry
|
||||
|
||||
|
||||
private const val TAG = "SpaSearchProvider"
|
||||
|
||||
@@ -42,18 +42,25 @@ private const val TAG = "SpaSearchProvider"
|
||||
* The content provider to return entry related data, which can be used for search and hierarchy.
|
||||
* One can query the provider result by:
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
|
||||
* For gallery, AuthorityPath = com.android.spa.gallery.provider
|
||||
* For Settings, AuthorityPath = com.android.settings.spa.provider
|
||||
* For gallery, AuthorityPath = com.android.spa.gallery.search.provider
|
||||
* For Settings, AuthorityPath = com.android.settings.spa.search.provider"
|
||||
* Some examples:
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_static
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_dynamic
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_static_data
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_data
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
|
||||
* $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
|
||||
*/
|
||||
class SpaSearchProvider : ContentProvider() {
|
||||
private val spaEnvironment get() = SpaEnvironmentFactory.instance
|
||||
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
|
||||
|
||||
private val queryMatchCode = mapOf(
|
||||
SEARCH_STATIC_DATA to 301,
|
||||
SEARCH_DYNAMIC_DATA to 302,
|
||||
SEARCH_MUTABLE_STATUS to 303,
|
||||
SEARCH_IMMUTABLE_STATUS to 304
|
||||
)
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
TODO("Implement this to handle requests to delete one or more rows")
|
||||
}
|
||||
@@ -85,10 +92,9 @@ class SpaSearchProvider : ContentProvider() {
|
||||
|
||||
override fun attachInfo(context: Context?, info: ProviderInfo?) {
|
||||
if (info != null) {
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority)
|
||||
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)
|
||||
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
|
||||
for (entry in queryMatchCode) {
|
||||
uriMatcher.addURI(info.authority, entry.key, entry.value)
|
||||
}
|
||||
}
|
||||
super.attachInfo(context, info)
|
||||
}
|
||||
@@ -102,11 +108,11 @@ class SpaSearchProvider : ContentProvider() {
|
||||
): Cursor? {
|
||||
return try {
|
||||
when (uriMatcher.match(uri)) {
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()
|
||||
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData()
|
||||
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
|
||||
queryMatchCode[SEARCH_STATIC_DATA] -> querySearchStaticData()
|
||||
queryMatchCode[SEARCH_DYNAMIC_DATA] -> querySearchDynamicData()
|
||||
queryMatchCode[SEARCH_MUTABLE_STATUS] ->
|
||||
querySearchMutableStatusData()
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
|
||||
queryMatchCode[SEARCH_IMMUTABLE_STATUS] ->
|
||||
querySearchImmutableStatusData()
|
||||
else -> throw UnsupportedOperationException("Unknown Uri $uri")
|
||||
}
|
||||
@@ -167,23 +173,45 @@ class SpaSearchProvider : ContentProvider() {
|
||||
|
||||
// Fetch search data. We can add runtime arguments later if necessary
|
||||
val searchData = entry.getSearchData() ?: return
|
||||
val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
|
||||
cursor.newRow()
|
||||
.add(ColumnEnum.ENTRY_ID.id, entry.id)
|
||||
.add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
|
||||
val intent = entry.createIntent(SESSION_SEARCH)
|
||||
val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
|
||||
.add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
|
||||
.add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
|
||||
.add(
|
||||
ColumnEnum.SEARCH_PATH.id,
|
||||
entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
|
||||
)
|
||||
intent?.let {
|
||||
row.add(ColumnEnum.INTENT_TARGET_PACKAGE.id, spaEnvironment.appContext.packageName)
|
||||
.add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
|
||||
.add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
|
||||
}
|
||||
if (entry.hasSliceSupport)
|
||||
row.add(
|
||||
ColumnEnum.SLICE_URI.id, Uri.Builder()
|
||||
.fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
|
||||
)
|
||||
// TODO: support legacy key
|
||||
}
|
||||
|
||||
private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
|
||||
// Fetch status data. We can add runtime arguments later if necessary
|
||||
val statusData = entry.getStatusData() ?: return
|
||||
val statusData = entry.getStatusData() ?: EntryStatusData()
|
||||
cursor.newRow()
|
||||
.add(ColumnEnum.ENTRY_ID.id, entry.id)
|
||||
.add(ColumnEnum.SEARCH_STATUS_DISABLED.id, statusData.isDisabled)
|
||||
.add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
|
||||
}
|
||||
|
||||
private fun QueryEnum.getColumns(): Array<String> {
|
||||
return columnNames.map { it.id }.toTypedArray()
|
||||
}
|
||||
|
||||
private fun marshall(parcelable: Parcelable?): ByteArray? {
|
||||
if (parcelable == null) return null
|
||||
val parcel = Parcel.obtain()
|
||||
parcelable.writeToParcel(parcel, 0)
|
||||
val bytes = parcel.marshall()
|
||||
parcel.recycle()
|
||||
return bytes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,14 @@ package com.android.settingslib.spa.search
|
||||
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.os.Bundle
|
||||
import android.os.Parcel
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.android.settingslib.spa.framework.common.ColumnEnum
|
||||
import com.android.settingslib.spa.framework.common.QueryEnum
|
||||
import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
|
||||
import com.android.settingslib.spa.framework.common.SettingsPage
|
||||
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
|
||||
import com.android.settingslib.spa.framework.common.createSettingsPage
|
||||
import com.android.settingslib.spa.framework.common.getIndex
|
||||
import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
|
||||
import com.android.settingslib.spa.tests.testutils.SppForSearch
|
||||
import com.google.common.truth.Truth
|
||||
@@ -39,51 +38,145 @@ class SpaSearchProviderTest {
|
||||
private val spaEnvironment =
|
||||
SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
|
||||
private val searchProvider = SpaSearchProvider()
|
||||
private val pageOwner = spaEnvironment.createPage("SppForSearch")
|
||||
|
||||
@Test
|
||||
fun testQuerySearchStatusData() {
|
||||
SpaEnvironmentFactory.reset(spaEnvironment)
|
||||
val pageOwner = spaEnvironment.createPage("SppForSearch")
|
||||
|
||||
val immutableStatus = searchProvider.querySearchImmutableStatusData()
|
||||
Truth.assertThat(immutableStatus.count).isEqualTo(1)
|
||||
Truth.assertThat(immutableStatus.count).isEqualTo(2)
|
||||
immutableStatus.moveToFirst()
|
||||
immutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchStaticWithNoStatus")
|
||||
)
|
||||
immutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_DISABLED,
|
||||
false.toString()
|
||||
)
|
||||
|
||||
immutableStatus.moveToNext()
|
||||
immutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
|
||||
)
|
||||
immutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
|
||||
)
|
||||
|
||||
val mutableStatus = searchProvider.querySearchMutableStatusData()
|
||||
Truth.assertThat(mutableStatus.count).isEqualTo(2)
|
||||
mutableStatus.moveToFirst()
|
||||
mutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
|
||||
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchStaticWithMutableStatus")
|
||||
)
|
||||
mutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
|
||||
)
|
||||
|
||||
mutableStatus.moveToNext()
|
||||
mutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
|
||||
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
|
||||
)
|
||||
mutableStatus.checkValue(
|
||||
QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testQuerySearchIndexData() {
|
||||
SpaEnvironmentFactory.reset(spaEnvironment)
|
||||
|
||||
val staticData = searchProvider.querySearchStaticData()
|
||||
Truth.assertThat(staticData.count).isEqualTo(2)
|
||||
staticData.moveToFirst()
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchStaticWithNoStatus")
|
||||
)
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
|
||||
)
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
|
||||
)
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY,
|
||||
ColumnEnum.SEARCH_PATH,
|
||||
listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
|
||||
)
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY,
|
||||
ColumnEnum.INTENT_TARGET_PACKAGE,
|
||||
spaEnvironment.appContext.packageName
|
||||
)
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY,
|
||||
ColumnEnum.INTENT_TARGET_CLASS,
|
||||
"com.android.settingslib.spa.tests.testutils.BlankActivity"
|
||||
)
|
||||
|
||||
// Check extras in intent
|
||||
val bundle =
|
||||
staticData.getExtras(QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.INTENT_EXTRAS)
|
||||
Truth.assertThat(bundle).isNotNull()
|
||||
Truth.assertThat(bundle!!.size()).isEqualTo(3)
|
||||
Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
|
||||
Truth.assertThat(bundle.getString("highlightEntry"))
|
||||
.isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
|
||||
Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
|
||||
|
||||
staticData.moveToNext()
|
||||
staticData.checkValue(
|
||||
QueryEnum.SEARCH_STATIC_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchStaticWithMutableStatus")
|
||||
)
|
||||
|
||||
val dynamicData = searchProvider.querySearchDynamicData()
|
||||
Truth.assertThat(dynamicData.count).isEqualTo(2)
|
||||
dynamicData.moveToFirst()
|
||||
dynamicData.checkValue(
|
||||
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchDynamicWithMutableStatus")
|
||||
)
|
||||
|
||||
dynamicData.moveToNext()
|
||||
dynamicData.checkValue(
|
||||
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
|
||||
ColumnEnum.ENTRY_ID,
|
||||
pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
|
||||
)
|
||||
dynamicData.checkValue(
|
||||
QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
|
||||
ColumnEnum.SEARCH_KEYWORD,
|
||||
listOf("kw1", "kw2").toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
|
||||
Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
|
||||
Truth.assertThat(getString(query.columnNames.indexOf(column))).isEqualTo(value)
|
||||
}
|
||||
|
||||
private fun Cursor.getExtras(query: QueryEnum, column: ColumnEnum): Bundle? {
|
||||
val extrasByte = getBlob(query.columnNames.indexOf(column)) ?: return null
|
||||
val parcel = Parcel.obtain()
|
||||
parcel.unmarshall(extrasByte, 0, extrasByte.size)
|
||||
parcel.setDataPosition(0)
|
||||
val bundle = Bundle()
|
||||
bundle.readFromParcel(parcel)
|
||||
return bundle
|
||||
}
|
||||
|
||||
private fun SettingsPage.getEntryId(name: String): String {
|
||||
|
||||
Reference in New Issue
Block a user