La búsqueda de configuración le permite buscar y cambiar configuraciones específicas de manera rápida y sencilla en la aplicación Configuración automotriz sin tener que navegar por los menús de la aplicación para encontrarla. La búsqueda es la forma más eficaz de encontrar una configuración específica. De forma predeterminada, la búsqueda solo encuentra configuraciones de AOSP. Las configuraciones adicionales, ya sean inyectadas o no, requieren cambios adicionales para poder indexarse.
Requisitos
Para que una configuración sea indexable mediante la búsqueda de Configuración, los datos deben provenir de:
- Fragmento
SearchIndexable
dentro deCarSettings
. - Aplicación a nivel de sistema.
Definir los datos
Campos comunes:
-
Key
. ( Obligatorio ) Clave de cadena única legible por humanos para identificar el resultado. -
IconResId
. Opcional Si aparece un icono 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 el resultado de la búsqueda. -
IntentTargetPackage
. Obligatorio siIntentAction
no está definido. Define el paquete en el que se resolverá el resultado de la búsqueda. -
IntentTargetClass
. Obligatorio siIntentAction
no está definido. Define la clase (actividad) en la que se resolverá el resultado de la búsqueda.
SearchIndexableResource
únicamente:
-
XmlResId
. ( Obligatorio ) Define la identificación del recurso XML de la página que contiene los resultados que se van a indexar.
SearchIndexableRaw
solamente:
-
Title
. ( Obligatorio ) 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 aparece en Buscar a menos que esté marcado de otra manera. Si bien los resultados de la búsqueda estática se almacenan en caché, cada vez que se abre la búsqueda se recupera una lista nueva de claves no indexables. Las razones para ocultar los resultados pueden incluir:
- Duplicar. Por ejemplo, aparece en varias páginas.
- Sólo se muestra de forma condicional. 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 sólo 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 la búsqueda de getNonIndexableKeys
. La clave siempre se puede devolver (casos de páginas duplicadas y con plantilla) o agregarse condicionalmente (sin casos 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 físico. Los datos estáticos se indexan solo una vez cuando se inicia por primera vez la búsqueda de Configuración.
Para los indexables dentro de la configuración, implemente getXmlResourcesToIndex
y/o getRawDataToIndex
. Para configuraciones inyectadas, implemente los métodos queryXmlResources
y/o queryRawData
.
índice dinámico
Si los datos indexables se pueden actualizar en consecuencia, utilice el método dinámico para indexar sus datos. La búsqueda de configuración actualiza esta lista dinámica cuando se inicia.
Para indexables dentro de la configuración, implemente getDynamicRawDataToIndex
. Para configuraciones inyectadas, implemente los queryDynamicRawData methods
.
Índice en la configuración del automóvil
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 resultados. La interfaz principal que se debe cumplir es SearchIndexablesProvider
, que utiliza SettingsIntelligence
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
estaría contento. Para que el proceso sea fácil de mantener y agregar nuevos fragmentos, utilice la generación de código SettingsLib
de SearchIndexableResources
. Específicamente para la configuración del automóvil, cada fragmento indexable está anotado 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
provocan 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 una envoltura delgada 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 básica 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 is suppressed during search query. */ protected boolean isPageSearchEnabled(Context context) { return true; } }
Indexar un nuevo fragmento
Con este diseño, es relativamente fácil agregar un nuevo SettingsFragment
para indexar, generalmente una actualización de dos líneas que proporciona el XML del 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 AAOS de SearchIndexablesProvider
, que utiliza SearchIndexableResources
y realiza la traducción de SearchIndexProviders
al esquema de la base de datos para SettingsIntelligence
, pero es independiente de qué fragmentos se indexan. SettingsIntelligence
admite cualquier cantidad 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. Las preferencias definidas en PreferenceScreen
para el fragmento deben tener cada una una clave única asignada para poder ser indexadas. Además, PreferenceScreen
debe tener una clave asignada para que se indexe el título de la pantalla.
Ejemplos de índice
En algunos casos, es posible que un fragmento no tenga una acción de intención específica asociada. En tales casos, es posible pasar la clase de actividad al intent CarBaseSearchIndexProvider
usando 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 pueda exportar.
@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 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; } }; }
Configuración inyectada de índice
Para inyectar una configuración para indexar:
- Defina un
SearchIndexablesProvider
para su aplicación extendiendo la claseandroid.provider.SearchIndexablesProvider
. - Actualice
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 proveedor de SearchIndexables
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; } } }