La búsqueda de configuración le permite buscar y cambiar rápida y fácilmente configuraciones específicas en la aplicación Configuración automotriz sin navegar a través de los menús de la aplicación para encontrarla. La búsqueda es la forma más efectiva de encontrar una configuración específica. De forma predeterminada, la búsqueda solo encuentra la configuración de AOSP. Las configuraciones adicionales, ya sea inyectadas o no, requieren cambios adicionales para ser indexadas.
Visión general
Requisitos
Para que una configuración sea indexable mediante la búsqueda de configuraciones, los datos deben provenir de:
- Fragmento
SearchIndexable
dentroCarSettings
. - Aplicación a nivel de sistema.
Definición de los datos
Campos comunes:
-
Key
( Obligatorio ) Clave única de cadena legible por humanos para identificar el resultado. -
IconResId
. Opcional Si aparece un ícono en su aplicación junto al resultado, agregue la identificación del recurso; de lo contrario, ignórelo. -
IntentAction
. Obligatorio siIntentTargetPackage
oIntentTargetClass
no están definidos. Define la acción que debe realizar la intención del resultado de la búsqueda. -
IntentTargetPackage
. Obligatorio siIntentAction
no está definido. Define el paquete en el que se resolverá la intención del resultado de la búsqueda. -
IntentTargetClass
. Obligatorio siIntentAction
no está definido. Define la clase (actividad) a la que se debe resolver la intención del resultado de la búsqueda.
SearchIndexableResource
solamente:
-
XmlResId
. ( Obligatorio ) Define la identificación del recurso XML de la página que contiene los resultados que se indexarán.
SearchIndexableRaw
solamente:
-
Title
( Requerido ) Título del resultado de la búsqueda. -
SummaryOn
. ( Opcional ) Resumen del resultado de la búsqueda. -
Keywords
( Opcional ) Lista de palabras asociadas al resultado de la búsqueda. Hace coincidir la consulta con su resultado. -
ScreenTitle
. ( Opcional ) Título de la página con el resultado de su búsqueda.
ocultar datos
Cada resultado de búsqueda aparecerá en Buscar a menos que se indique lo contrario. Si bien los resultados de la búsqueda estática se almacenan en caché, se recupera una nueva lista de claves no indexables cada vez que se abre la búsqueda. Las razones para ocultar los resultados pueden incluir:
- Duplicar. Por ejemplo, aparece en varias páginas.
- Solo se muestra condicionalmente. Por ejemplo, solo muestra la configuración de datos móviles cuando hay una tarjeta SIM presente).
- Página con plantilla. Por ejemplo, una página de detalles para una aplicación individual.
- La configuración necesita más contexto que un título y un subtítulo. Por ejemplo, una configuración de "Configuración", que solo es relevante para el título de la pantalla.
Para ocultar una configuración, su proveedor o SEARCH_INDEX_DATA_PROVIDER
debe devolver la clave del resultado de búsqueda de getNonIndexableKeys
. La clave siempre se puede devolver (casos de página con plantilla duplicada) o agregarse condicionalmente (sin caso de datos móviles).
índice estático
Utilice el índice estático si los datos de su índice son siempre los mismos. Por ejemplo, el título y el resumen de los datos XML o los datos sin procesar del código duro. Los datos estáticos se indexarán solo una vez cuando se inicie la búsqueda de Configuración por primera vez.
Para indexables dentro de la configuración, implemente getXmlResourcesToIndex
y/o getRawDataToIndex
. Para configuraciones inyectadas, implemente los queryXmlResources
y/o queryRawData
.
índice dinámico
Si los datos indexables se pueden actualizar en consecuencia, use el método dinámico para indexar sus datos. La búsqueda de configuraciones actualizará esta lista dinámica cuando se inicie.
Para indexables dentro de la configuración, implemente getDynamicRawDataToIndex
. Para la configuración inyectada, implemente los queryDynamicRawData methods
.
Indexación en la configuración del automóvil
Visión general
Para indexar diferentes configuraciones que se incluirán en la función de búsqueda, consulte Proveedores de contenido . Los paquetes SettingsLib
y android.provider
ya tienen interfaces definidas y clases abstractas para ampliar y proporcionar nuevas entradas para indexar. En la configuración de AOSP, las implementaciones de estas clases se utilizan para indexar los resultados. La interfaz principal que debe cumplirse es SearchIndexablesProvider
, que SettingsIntelligence
usa para indexar datos.
public abstract class SearchIndexablesProvider extends ContentProvider { public abstract Cursor queryXmlResources(String[] projection); public abstract Cursor queryRawData(String[] projection); public abstract Cursor queryNonIndexableKeys(String[] projection); }
En teoría, cada fragmento podría agregarse a los resultados en SearchIndexablesProvider
, en cuyo caso SettingsIntelligence
sería el contenido. Para facilitar el mantenimiento del proceso para agregar nuevos fragmentos, use la generación de código SettingsLib
de SearchIndexableResources
. Específico para la configuración del automóvil, cada fragmento indexable se anota con @SearchIndexable
y luego tiene un campo SearchIndexProvider
estático que proporciona los datos relevantes para ese fragmento. Los fragmentos con la anotación pero sin un SearchIndexProvider
dan como resultado un error de compilación.
interface SearchIndexProvider { List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled); List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled); List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled); List<String> getNonIndexableKeys(Context context); }
Luego, todos estos fragmentos se agregan a la clase SearchIndexableResourcesAuto
generada automáticamente, que es un contenedor delgado alrededor de la lista de campos SearchIndexProvider
para todos los fragmentos. Se proporciona soporte para especificar un objetivo específico para una anotación (como Auto, TV y Wear), sin embargo, la mayoría de las anotaciones se dejan en el valor predeterminado ( All
). En este caso de uso, no existe una necesidad específica de especificar el destino automático, por lo que permanece como All
. La implementación base de SearchIndexProvider
pretende ser suficiente para la mayoría de los fragmentos.
public class CarBaseSearchIndexProvider implements Indexable.SearchIndexProvider { private static final Logger LOG = new Logger(CarBaseSearchIndexProvider.class); private final int mXmlRes; private final String mIntentAction; private final String mIntentClass; public CarBaseSearchIndexProvider(@XmlRes int xmlRes, String intentAction) { mXmlRes = xmlRes; mIntentAction = intentAction; mIntentClass = null; } public CarBaseSearchIndexProvider(@XmlRes int xmlRes, @NonNull Class intentClass) { mXmlRes = xmlRes; mIntentAction = null; mIntentClass = intentClass.getName(); } @Override public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { SearchIndexableResource sir = new SearchIndexableResource(context); sir.xmlResId = mXmlRes; sir.intentAction = mIntentAction; sir.intentTargetPackage = context.getPackageName(); sir.intentTargetClass = mIntentClass; return Collections.singletonList(sir); } @Override public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { return null; } @Override public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) { return null; } @Override public List<String> getNonIndexableKeys(Context context) { if (!isPageSearchEnabled(context)) { try { return PreferenceXmlParser.extractMetadata(context, mXmlRes, FLAG_NEED_KEY) .stream() .map(bundle -> bundle.getString(METADATA_KEY)) .collect(Collectors.toList()); } catch (IOException | XmlPullParserException e) { LOG.w("Error parsing non-indexable XML - " + mXmlRes); } } return null; } /** * Returns true if the page should be considered in search query. If return false, entire page will be suppressed during search query. */ protected boolean isPageSearchEnabled(Context context) { return true; } }
Indexación de un nuevo fragmento
Con este diseño, es relativamente fácil agregar un nuevo SettingsFragment
para indexarlo, generalmente una actualización de dos líneas que proporciona el XML para el fragmento y la intención a seguir. Con WifiSettingsFragment
como ejemplo:
@SearchIndexable public class WifiSettingsFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.wifi_list_fragment, Settings.ACTION_WIFI_SETTINGS); }
La implementación de AAOS de SearchIndexablesProvider
, que usa SearchIndexableResources
y hace la traducción de SearchIndexProviders
al esquema de la base de datos para SettingsIntelligence
, pero es independiente de los fragmentos que se indexan. SettingsIntelligence
admite cualquier número de proveedores, por lo que se pueden crear nuevos proveedores para admitir casos de uso especializados, lo que permite que cada uno se especialice y se centre en resultados con estructuras similares. Cada una de las preferencias definidas en PreferenceScreen
para el fragmento debe tener una clave única asignada para poder indexarse. Además, PreferenceScreen
debe tener una clave asignada para que se indexe el título de la pantalla.
Ejemplos de indexación
En algunos casos, es posible que un fragmento no tenga asociada una acción de intención específica. En tales casos, es posible pasar la clase de actividad a la intención CarBaseSearchIndexProvider
utilizando un componente en lugar de una acción. Esto aún requiere que la actividad esté presente en el archivo de manifiesto y que se exporte.
@SearchIndexable public class LanguagesAndInputFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment, LanguagesAndInputActivity.class); }
En algunos casos especiales, es posible que sea necesario anular algunos métodos de CarBaseSearchIndexProvider
para obtener los resultados deseados que se indexarán. Por ejemplo, en NetworkAndInternetFragment
, las preferencias relacionadas con la red móvil no debían indexarse en dispositivos sin red móvil. En este caso, anule el método getNonIndexableKeys
y marque las claves apropiadas como no indexables cuando un dispositivo no tenga una red móvil.
@SearchIndexable public class NetworkAndInternetFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.network_and_internet_fragment, Settings.Panel.ACTION_INTERNET_CONNECTIVITY) { @Override public List<String> getNonIndexableKeys(Context context) { if (!NetworkUtils.hasMobileNetwork( context.getSystemService(ConnectivityManager.class))) { List<String> nonIndexableKeys = new ArrayList<>(); nonIndexableKeys.add(context.getString( R.string.pk_mobile_network_settings_entry)); nonIndexableKeys.add(context.getString( R.string.pk_data_usage_settings_entry)); return nonIndexableKeys; } return null; } }; }
Dependiendo de las necesidades del fragmento en particular, se pueden anular otros métodos de CarBaseSearchIndexProvider
para incluir otros datos indexables, como datos sin procesar estáticos y dinámicos.
@SearchIndexable public class RawIndexDemoFragment extends SettingsFragment { public static final String KEY_CUSTOM_RESULT = "custom_result_key"; [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.raw_index_demo_fragment, RawIndexDemoActivity.class) { @Override public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { List<SearchIndexableRaw> rawData = new ArrayList<>(); SearchIndexableRaw customResult = new SearchIndexableRaw(context); customResult.key = KEY_CUSTOM_RESULT; customResult.title = context.getString(R.string.my_title); customResult.screenTitle = context.getString(R.string.my_screen_title); rawData.add(customResult); return rawData; } @Override public List<SearchIndexableRaw> getDynamicRawDataToIndex(Context context, boolean enabled) { List<SearchIndexableRaw> rawData = new ArrayList<>(); SearchIndexableRaw customResult = new SearchIndexableRaw(context); if (hasIndexData()) { customResult.key = KEY_CUSTOM_RESULT; customResult.title = context.getString(R.string.my_title); customResult.screenTitle = context.getString(R.string.my_screen_title); } rawData.add(customResult); return rawData; } }; }
Indexación de configuraciones inyectadas
Visión general
Para inyectar una configuración para ser indexada:
- Defina un
SearchIndexablesProvider
para su aplicación extendiendo la claseandroid.provider.SearchIndexablesProvider
. - Actualice el
AndroidManifest.xml
de la aplicación con el proveedor en el Paso 1. El formato es:<provider android:name="PROVIDER_CLASS_NAME" android:authorities="PROVIDER_AUTHORITY" android:multiprocess="false" android:grantUriPermissions="true" android:permission="android.permission.READ_SEARCH_INDEXABLES" android:exported="true"> <intent-filter> <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" /> </intent-filter> </provider>
- Agregue datos indexables a su proveedor. La implementación depende de las necesidades de la aplicación. Se pueden indexar dos tipos de datos diferentes:
SearchIndexableResource
ySearchIndexableRaw
.
Ejemplo de SearchIndexablesProvider
public class SearchDemoProvider extends SearchIndexablesProvider { /** * Key for Auto brightness setting. */ public static final String KEY_AUTO_BRIGHTNESS = "auto_brightness"; /** * Key for my magic preference. */ public static final String KEY_MY_PREFERENCE = "my_preference_key"; /** * Key for my custom search result. */ public static final String KEY_CUSTOM_RESULT = "custom_result_key"; private String mPackageName; @Override public boolean onCreate() { mPackageName = getContext().getPackageName(); return true; } @Override public Cursor queryXmlResources(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS); cursor.addRow(getResourceRow(R.xml.demo_xml)); return cursor; } @Override public Cursor queryRawData(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS); Context context = getContext(); Object[] raw = new Object[INDEXABLES_RAW_COLUMNS.length]; raw[COLUMN_INDEX_RAW_TITLE] = context.getString(R.string.my_title); raw[COLUMN_INDEX_RAW_SUMMARY_ON] = context.getString(R.string.my_summary); raw[COLUMN_INDEX_RAW_KEYWORDS] = context.getString(R.string.my_keywords); raw[COLUMN_INDEX_RAW_SCREEN_TITLE] = context.getString(R.string.my_screen_title); raw[COLUMN_INDEX_RAW_KEY] = KEY_CUSTOM_RESULT; raw[COLUMN_INDEX_RAW_INTENT_ACTION] = Intent.ACTION_MAIN; raw[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = mPackageName; raw[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = MyDemoFragment.class.getName(); cursor.addRow(raw); return cursor; } @Override public Cursor queryDynamicRawData(String[] projection) { MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS); DemoObject object = getDynamicIndexData(); Object[] raw = new Object[INDEXABLES_RAW_COLUMNS.length]; raw[COLUMN_INDEX_RAW_KEY] = object.key; raw[COLUMN_INDEX_RAW_TITLE] = object.title; raw[COLUMN_INDEX_RAW_KEYWORDS] = object.keywords; raw[COLUMN_INDEX_RAW_INTENT_ACTION] = object.intentAction; raw[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = object.mPackageName; raw[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = object.className; cursor.addRow(raw); return cursor; } @Override public Cursor queryNonIndexableKeys(String[] projection) { MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS); cursor.addRow(getNonIndexableRow(KEY_AUTO_BRIGHTNESS)); if (!Utils.isMyPreferenceAvailable) { cursor.addRow(getNonIndexableRow(KEY_MY_PREFERENCE)); } return cursor; } private Object[] getResourceRow(int xmlResId) { Object[] row = new Object[INDEXABLES_XML_RES_COLUMNS.length]; row[COLUMN_INDEX_XML_RES_RESID] = xmlResId; row[COLUMN_INDEX_XML_RES_ICON_RESID] = 0; row[COLUMN_INDEX_XML_RES_INTENT_ACTION] = Intent.ACTION_MAIN; row[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = mPackageName; row[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = SearchResult.class.getName(); return row; } private Object[] getNonIndexableRow(String key) { final Object[] ref = new Object[NON_INDEXABLES_KEYS_COLUMNS.length]; ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = key; return ref; } private DemoObject getDynamicIndexData() { if (hasIndexData) { DemoObject object = new DemoObject(); object.key = "demo key"; object.title = "demo title"; object.keywords = "demo, keywords"; object.intentAction = "com.demo.DYNAMIC_INDEX"; object.packageName = "com.demo"; object.className = "DemoClass"; return object; } } }