Indexación de búsqueda de la configuración del vehículo

La búsqueda de Configuración te permite buscar y cambiar de forma rápida y sencilla de la app de Configuración de Automotive sin navegar por los menús encuéntrala. La búsqueda es la forma más eficaz de encontrar un entorno específico. De forma predeterminada, búsqueda solo encuentra la configuración de AOSP. La configuración adicional, ya sea que se inserte o no, requieren cambios adicionales para poder indexarse.

Requisitos

Para que la búsqueda de configuración pueda indexar un parámetro de configuración, los datos deben provenir de una de las siguientes fuentes:

  • SearchIndexable dentro de CarSettings.
  • App a nivel del sistema

Define los datos

Campos comunes:

  • Key (Obligatorio) Clave de cadena única legible por humanos para identificar el resultado.
  • IconResId Opcional: Si aparece un ícono en la aplicación junto al , luego agrega el ID de recurso; de lo contrario, ignóralo.
  • IntentAction Obligatorio si IntentTargetPackage o IntentTargetClass no está definido. Define la acción en la que el resultado de la búsqueda es realizar.
  • IntentTargetPackage Obligatorio si no es IntentAction definido. Define el paquete en el que se resolverá el intent del resultado de la búsqueda.
  • IntentTargetClass Obligatorio si no es IntentAction definido. Define la clase (actividad) en la que debe resolverse el intent del resultado de la búsqueda.

Solo SearchIndexableResource:

  • XmlResId Define el ID de recurso XML de la página que contiene (obligatorio). los resultados que se indexarán.

Solo SearchIndexableRaw:

  • Title (Obligatorio) Es el título del resultado de la búsqueda.
  • SummaryOn Resumen del resultado de la búsqueda (opcional).
  • Keywords (Opcional) Lista de palabras asociadas con el resultado de la búsqueda. Hace coincidir la consulta con tu resultado.
  • ScreenTitle (Opcional) Es el título de la página con el resultado de la búsqueda.

Ocultar datos

Cada resultado de la búsqueda aparece en la Búsqueda, a menos que se indique lo contrario. Mientras que está estático los resultados de la búsqueda se almacenan en caché, se recupera una lista nueva de claves no indexables cada vez se abre la búsqueda. Entre los motivos para ocultar resultados, se pueden incluir los siguientes:

  • Duplicar. Por ejemplo, que aparezcan en varias páginas.
  • Solo se muestra de manera condicional. Por ejemplo, solo muestra la configuración de datos móviles. si hay una tarjeta SIM).
  • Página con plantilla. Por ejemplo, la página de detalles de una aplicación individual.
  • La configuración necesita más contexto que un título y un subtítulo. Para Por ejemplo, la sección "Configuración" que solo es relevante para el título de la pantalla.

Para ocultar un parámetro de configuración, tu proveedor o SEARCH_INDEX_DATA_PROVIDER deben Muestra la clave del resultado de la búsqueda desde getNonIndexableKeys. La clave puede Siempre se devolverán (casos de páginas duplicados o basados en plantillas) o se agregarán de forma condicional (sin dispositivos móviles). caso de datos).

Índice estático

Usa el índice estático si tus datos de índice son siempre iguales. Por ejemplo, el título un resumen de los datos XML o el código rígido de los datos sin procesar. Los datos estáticos se indexan solo una vez cuando se inicia la búsqueda de Configuración por primera vez.

Para los indexables dentro de la configuración, implementa getXmlResourcesToIndex o getRawDataToIndex. Para la configuración insertada, implementa el queryXmlResources o queryRawData.

Índice dinámico

Si los datos indexables pueden actualizarse según corresponda, usa el método dinámico para indexar tus datos. La búsqueda de configuración actualiza esta lista dinámica cuando se lanza.

Para los indexables dentro de la configuración, implementa getDynamicRawDataToIndex. Para la configuración insertada, implementa queryDynamicRawData methods.

Índice en la configuración del vehículo

Si quieres indexar los diferentes parámetros de configuración que se incluirán en la función de búsqueda, consulta la siguiente información: Contenido del proveedor de servicios en la nube. SettingsLib y android.provider paquetes ya tienen interfaces definidas y clases abstractas para extender y proporciona entradas nuevas para indexar. En la configuración del AOSP, las implementaciones se usan para indexar resultados. La interfaz principal que se debe entregar es SearchIndexablesProvider, que usa 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 sería contenido. Para que el proceso de agregar fragmentos nuevos sea fácil de mantener, usa la generación de código SettingsLib de SearchIndexableResources. En particular para la configuración del vehículo, cada fragmento indexable se anota con @SearchIndexable y, luego, tiene un elemento SearchIndexProvider estático que proporciona los datos relevantes para ese fragmento. Los fragmentos con el anotación, pero falta un resultado SearchIndexProvider en una 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);
    }

Todos esos fragmentos se agregan al archivo Clase SearchIndexableResourcesAuto, que es un wrapper delgado alrededor de la lista de campos SearchIndexProvider para todos los fragmentos. Se proporciona compatibilidad para especificar un destino específico para una anotación (como Auto, TV y Wear), sin embargo, la mayoría de las anotaciones se dejan con la configuración predeterminada (All). En este caso de uso, no es necesario especificar la segmentación automática, por lo que se mantiene como All. La implementación base de SearchIndexProvider está destinado a 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;
    }
}

Cómo indexar un nuevo fragmento

Con este diseño, es relativamente fácil agregar un SettingsFragment nuevo. por lo general, una actualización de dos líneas que proporciona el XML del fragmento y del que se debe 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 realiza la traducción del SearchIndexProviders en el esquema de la base de datos para SettingsIntelligence, pero es independiente de lo que son los fragmentos que se está indexando. SettingsIntelligence admite cualquier cantidad de para que puedan crearse nuevos proveedores que respalden el uso especializado lo que permite que cada uno se especializa y se enfoca en resultados con de las estructuras de datos. Las preferencias definidas en PreferenceScreen del fragmento debe tener asignada una clave única para que pueda de forma manual. Además, PreferenceScreen debe tener una clave para el título de la pantalla que se indexará.

Ejemplos de índices

En algunos casos, es posible que un fragmento no tenga asociada una acción de intent específica. con él. En esos casos, es posible pasar la clase de actividad a la clase CarBaseSearchIndexProvider con un componente, en lugar de una acción. Esto requiere que la actividad esté presente en el archivo de manifiesto y exportarlos.

@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, algunos métodos de CarBaseSearchIndexProvider que deba anularse para obtener los resultados deseados para su indexación. Por ejemplo, en NetworkAndInternetFragment, se aplicaron las preferencias relacionadas con la red móvil no debe indexarse en dispositivos sin red móvil. En este caso, anula el getNonIndexableKeys y marca las claves apropiadas como No se puede indexar cuando un dispositivo no tiene 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;
                }
            };
}

Según las necesidades del fragmento en particular, se pueden usar otros métodos del Es posible que CarBaseSearchIndexProvider se anule para incluir otros que se pueden indexar, 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 de índice insertado

Si deseas insertar una configuración para indexar, haz lo siguiente:

  1. Define un SearchIndexablesProvider para tu app extendiendo el Clase android.provider.SearchIndexablesProvider.
  2. Actualiza el AndroidManifest.xml de la app con el proveedor en el paso 1. El formato es el siguiente:
    <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>
    
  3. Agrega datos indexables a tu proveedor. La implementación depende de las necesidades la aplicación. Se pueden indexar dos tipos de datos diferentes: SearchIndexableResource y SearchIndexableRaw.

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;
        }
    }
}