Indicizzazione della ricerca delle impostazioni dell'auto

La ricerca delle impostazioni ti consente di cercare e modificare rapidamente e facilmente impostazioni specifiche nell'app Impostazioni automobilistiche senza dover navigare nei menu dell'app per trovarla. La ricerca è il modo più efficace per trovare un'impostazione specifica. Per impostazione predefinita, la ricerca trova solo le impostazioni AOSP. Impostazioni aggiuntive, inserite o meno, richiedono ulteriori modifiche per essere indicizzate.

Requisiti

Affinché un'impostazione sia indicizzabile tramite la ricerca Impostazioni, i dati devono provenire da:

  • Frammento SearchIndexable all'interno di CarSettings .
  • App a livello di sistema.

Definire i dati

Campi comuni:

  • Key . ( Obbligatorio ) Chiave stringa univoca leggibile dall'uomo per identificare il risultato.
  • IconResId . Facoltativo Se viene visualizzata un'icona nella tua app accanto al risultato, aggiungi l'ID della risorsa, altrimenti ignora.
  • IntentAction . Obbligatorio se IntentTargetPackage o IntentTargetClass non è definito. Definisce l'azione che deve essere eseguita dall'intento del risultato della ricerca.
  • IntentTargetPackage . Obbligatorio se IntentAction non è definito. Definisce il pacchetto in cui deve essere risolto l'intento del risultato della ricerca.
  • IntentTargetClass . Obbligatorio se IntentAction non è definito. Definisce la classe (attività) a cui deve risolvere l'intento del risultato della ricerca.

Solo SearchIndexableResource :

  • XmlResId . ( Obbligatorio ) Definisce l'ID della risorsa XML della pagina contenente i risultati da indicizzare.

Solo SearchIndexableRaw :

  • Title . ( Obbligatorio ) Titolo del risultato della ricerca.
  • SummaryOn . ( Facoltativo ) Riepilogo del risultato della ricerca.
  • Keywords . ( Facoltativo ) Elenco di parole associate al risultato della ricerca. Abbina la query al risultato.
  • ScreenTitle . ( Facoltativo ) Titolo della pagina con il risultato della ricerca.

Nascondi dati

Ogni risultato di ricerca viene visualizzato in Cerca a meno che non sia contrassegnato diversamente. Mentre i risultati della ricerca statica vengono memorizzati nella cache, ogni volta che viene aperta la ricerca viene recuperato un nuovo elenco di chiavi non indicizzabili. I motivi per nascondere i risultati possono includere:

  • Duplicare. Ad esempio, appare su più pagine.
  • Mostrato solo in modo condizionale. Ad esempio, mostra le impostazioni dei dati mobili solo quando è presente una scheda SIM).
  • Pagina basata su modelli. Ad esempio, una pagina dei dettagli per una singola app.
  • L'impostazione richiede più contesto di un titolo e un sottotitolo. Ad esempio, un'impostazione "Impostazioni", che è rilevante solo per il titolo della schermata.

Per nascondere un'impostazione, il tuo provider o SEARCH_INDEX_DATA_PROVIDER deve restituire la chiave del risultato della ricerca da getNonIndexableKeys . La chiave può sempre essere restituita (casi di pagina duplicati, basati su modello) o aggiunta in modo condizionale (nessun caso di dati mobili).

Indice statico

Utilizza l'indice statico se i dati dell'indice sono sempre gli stessi. Ad esempio, il titolo e il riepilogo dei dati XML o i dati grezzi codificati. I dati statici vengono indicizzati una sola volta al primo avvio della ricerca Impostazioni.

Per gli indicizzabili all'interno delle impostazioni, implementare getXmlResourcesToIndex e/o getRawDataToIndex . Per le impostazioni inserite, implementare i metodi queryXmlResources e/o queryRawData .

Indice dinamico

Se i dati indicizzabili possono essere aggiornati di conseguenza, utilizza il metodo dinamico per indicizzare i tuoi dati. La ricerca delle impostazioni aggiorna questo elenco dinamico quando viene avviato.

Per gli indicizzabili all'interno delle impostazioni, implementare getDynamicRawDataToIndex . Per le impostazioni inserite, implementare i queryDynamicRawData methods .

Indice nelle Impostazioni dell'auto

Per indicizzare le diverse impostazioni da includere nella funzione di ricerca, vedere Fornitori di contenuti . I pacchetti SettingsLib e android.provider dispongono già di interfacce definite e classi astratte da estendere per fornire nuove voci da indicizzare. Nelle impostazioni AOSP, le implementazioni di queste classi vengono utilizzate per indicizzare i risultati. L'interfaccia principale da soddisfare è SearchIndexablesProvider , utilizzata da SettingsIntelligence per indicizzare i dati.

public abstract class SearchIndexablesProvider extends ContentProvider {
    public abstract Cursor queryXmlResources(String[] projection);
    public abstract Cursor queryRawData(String[] projection);
    public abstract Cursor queryNonIndexableKeys(String[] projection);
}

In teoria, ogni frammento potrebbe essere aggiunto ai risultati in SearchIndexablesProvider , nel qual caso SettingsIntelligence sarebbe contenuto. Per semplificare la manutenzione del processo e aggiungere nuovi frammenti, utilizzare la generazione del codice SettingsLib di SearchIndexableResources . Specifico per Impostazioni auto, ogni frammento indicizzabile è annotato con @SearchIndexable e quindi ha un campo SearchIndexProvider statico che fornisce i dati rilevanti per quel frammento. I frammenti con l'annotazione ma in cui manca un SearchIndexProvider generano un errore di compilazione.

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

Tutti questi frammenti vengono quindi aggiunti alla classe SearchIndexableResourcesAuto generata automaticamente, che è un sottile wrapper attorno all'elenco dei campi SearchIndexProvider per tutti i frammenti. Viene fornito il supporto per specificare una destinazione specifica per un'annotazione (come Auto, TV e Usura), tuttavia la maggior parte delle annotazioni viene lasciata sul valore predefinito ( All ). In questo caso d'uso, non esiste alcuna necessità specifica di specificare il target Auto, quindi rimane All . L'implementazione di base di SearchIndexProvider è destinata ad essere sufficiente per la maggior parte dei frammenti.

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

Indicizzare un nuovo frammento

Con questa struttura, è relativamente semplice aggiungere un nuovo SettingsFragment da indicizzare, in genere un aggiornamento di due righe che fornisce l'XML per il frammento e l'intento da seguire. Con WifiSettingsFragment come esempio:

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

L'implementazione AAOS di SearchIndexablesProvider , che utilizza SearchIndexableResources ed esegue la traduzione da SearchIndexProviders nello schema del database per SettingsIntelligence , ma è indipendente da quali frammenti vengono indicizzati. SettingsIntelligence supporta un numero qualsiasi di provider, quindi è possibile creare nuovi provider per supportare casi d'uso specializzati, consentendo a ciascuno di essere specializzato e concentrato su risultati con strutture simili. A ciascuna delle preferenze definite nella PreferenceScreen per il frammento deve essere assegnata una chiave univoca per poter essere indicizzate. Inoltre, a PreferenceScreen deve essere assegnata una chiave affinché il titolo della schermata venga indicizzato.

Esempi di indice

In alcuni casi, a un frammento potrebbe non essere associata un'azione di intento specifica. In questi casi, è possibile passare la classe di attività all'intento CarBaseSearchIndexProvider utilizzando un componente anziché un'azione. Ciò richiede comunque che l'attività sia presente nel file manifest e che venga esportata.

@SearchIndexable
public class LanguagesAndInputFragment extends SettingsFragment {
[...]
    public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
        new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment,
                LanguagesAndInputActivity.class);
}

In alcuni casi speciali, potrebbe essere necessario sovrascrivere alcuni metodi di CarBaseSearchIndexProvider per ottenere i risultati desiderati da indicizzare. Ad esempio, in NetworkAndInternetFragment , le preferenze relative alla rete mobile non dovevano essere indicizzate sui dispositivi privi di rete mobile. In questo caso, sovrascrivi il metodo getNonIndexableKeys e contrassegna le chiavi appropriate come non indicizzabili quando un dispositivo non dispone di una rete mobile.

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

A seconda delle esigenze del particolare frammento, altri metodi di CarBaseSearchIndexProvider possono essere sovrascritti per includere altri dati indicizzabili, come dati grezzi statici e dinamici.

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

Impostazioni inserite nell'indice

Per inserire un'impostazione da indicizzare:

  1. Definisci un SearchIndexablesProvider per la tua app estendendo la classe android.provider.SearchIndexablesProvider .
  2. Aggiorna il AndroidManifest.xml dell'app con il provider nel passaggio 1. Il formato è:
    <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. Aggiungi dati indicizzabili al tuo provider. L'implementazione dipende dalle esigenze dell'app. È possibile indicizzare due diversi tipi di dati: SearchIndexableResource e SearchIndexableRaw .

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