Indicizzazione delle ricerche nelle impostazioni dell'auto

La ricerca nelle impostazioni consente di cercare e modificare in modo facile e veloce impostazioni nell'app Automotive Settings senza dover navigare nei menu delle app a trovarlo. 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 poter essere indicizzati.

Requisiti

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

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

Definisci i dati

Campi comuni:

  • Key. (Obbligatorio) Chiave di stringa univoca leggibile per identificare il risultato.
  • IconResId. Facoltativo Se nell'app viene visualizzata un'icona accanto a il risultato, quindi aggiungi l'ID risorsa, altrimenti ignoralo.
  • IntentAction. Obbligatorio se IntentTargetPackage o IntentTargetClass non è definito. Definisce l'azione che il risultato di ricerca è prendere.
  • IntentTargetPackage. Obbligatorio se IntentAction non è definito. Definisce il pacchetto in cui deve risolvere l'intento del risultato di ricerca.
  • IntentTargetClass. Obbligatorio se IntentAction non è definito. Definisce la classe (attività) in cui deve risolversi l'intento del risultato di ricerca.

Solo SearchIndexableResource:

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

Solo SearchIndexableRaw:

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

Nascondi dati

Ogni risultato di ricerca viene visualizzato nella Ricerca, a meno che non sia contrassegnato in modo diverso. Quando il veicolo è statico i risultati di ricerca vengono memorizzati nella cache e ogni volta viene recuperato un nuovo elenco di chiavi non indicizzabili si apre la ricerca. I motivi per nascondere i risultati possono includere:

  • Duplica. Ad esempio, appare su più pagine.
  • Mostrata solo in base alle condizioni. Ad esempio, mostra solo le impostazioni dei dati mobili quando è presente una scheda SIM).
  • Pagina basata su modelli. ad esempio una pagina dei dettagli di una singola app.
  • L'impostazione richiede più contesto rispetto a un titolo e un sottotitolo. Per ad esempio "Impostazioni" , che è pertinente solo al titolo della schermata.

Per nascondere un'impostazione, il tuo provider o SEARCH_INDEX_DATA_PROVIDER deve restituisce la chiave del risultato di ricerca di getNonIndexableKeys. La chiave può Deve essere sempre restituito (richieste di pagina duplicate o basate su modelli) o aggiunte in modo condizionale (nessuna caso dati).

Indice statico

Utilizza l'indice statico se i dati dell'indice sono sempre gli stessi. Ad esempio, il titolo e un riepilogo dei dati XML o dei dati non elaborati hardcoded. I dati statici sono indicizzati solo una volta, quando viene avviata la ricerca nelle impostazioni.

Per gli elementi indicizzabili all'interno delle impostazioni, implementa getXmlResourcesToIndex e/o getRawDataToIndex. Per le impostazioni inserite, implementa la classe metodi queryXmlResources e/o queryRawData.

Indice dinamico

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

Per gli elementi indicizzabili all'interno delle impostazioni, implementa getDynamicRawDataToIndex. Per le impostazioni inserite, implementa queryDynamicRawData methods.

Indice in Impostazioni auto

Per indicizzare le diverse impostazioni da includere nella funzionalità di ricerca, vedi Contenuti di terze parti. SettingsLib e android.provider i pacchetti hanno già interfacce definite e classi astratte per l'estensione fornendo nuove voci da indicizzare. Nelle impostazioni AOSP, le implementazioni utilizzate per indicizzare i risultati. L'interfaccia principale da completare è 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 sarebbero contenuti. Per semplificare la gestione del processo e aggiungere nuovi frammenti, utilizza la generazione del codice SettingsLib di SearchIndexableResources. Specifica per le Impostazioni auto, ogni frammento indicizzabile è annotato con @SearchIndexable e ha un valore SearchIndexProvider statico campo che fornisce i dati pertinenti per quel frammento. Frammenti con l'annotazione, ma manca un risultato SearchIndexProvider in una 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 ai campi SearchIndexableResourcesAuto, che è un wrapper sottile intorno all'elenco di SearchIndexProvider campi per tutti i frammenti. Viene fornita assistenza per specificare una destinazione specifica per un'annotazione (ad esempio Auto, TV e Wear), tuttavia la maggior parte delle annotazioni rimane sul valore predefinito (All). In questo caso d'uso, non esiste alcuna esigenza specifica di specificare il target automatico, quindi rimane come All. L'implementazione di base di SearchIndexProvider è pensato per 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;
    }
}

Indicizza un nuovo frammento

Con questo design, è relativamente facile aggiungere un nuovo SettingsFragment da indicizzare, di solito un aggiornamento di due righe che fornisce il codice XML per il frammento e l'intenzione di essere seguiti. 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 cosa sono i frammenti in fase di indicizzazione. SettingsIntelligence supporta un numero illimitato di così da crearne di nuovi per supportare l'uso specializzato di casi diversi, in modo che ciascuno di loro sia specializzato e si concentri su risultati le nostre strutture. Le preferenze definite nelle PreferenceScreen ogni frammento deve avere una chiave unica assegnata per poter essere indicizzati. Inoltre, PreferenceScreen deve avere una chiave assegnato per indicizzare il titolo della schermata.

Esempi di indice

In alcuni casi, a un frammento potrebbe non essere associata un'azione di intent specifica con essa. In questi casi, è possibile passare la classe di attività al CarBaseSearchIndexProvider intent che utilizza un componente anziché un'azione. L'attività deve comunque essere presente nel file manifest. e per l'esportazione.

@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, alcuni metodi di CarBaseSearchIndexProvider potrebbe essere necessario eseguire l'override per ottenere i risultati desiderati. Ad esempio, nel NetworkAndInternetFragment, le preferenze relative alla rete mobile sono state non vengano indicizzate su dispositivi senza rete mobile. In questo caso, sostituisci getNonIndexableKeys e contrassegna le chiavi appropriate come non indicizzabile 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 del CarBaseSearchIndexProvider può essere sostituito per includere altri come dati indicizzabili, come dati non elaborati 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 il valore android.provider.SearchIndexablesProvider corso.
  2. Aggiorna il valore AndroidManifest.xml dell'app con il fornitore 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 l'app. È possibile indicizzare due diversi tipi di dati: SearchIndexableResource e SearchIndexableRaw.

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