Indexierung der Suche in den Autoeinstellungen

Mit der Einstellungensuche können Sie schnell und einfach nach bestimmten Einstellungen in den Einstellungen für die Mobilität suchen und sie ändern, ohne durch die App-Menüs scrollen zu müssen. Die Suche ist die effektivste Methode, um eine bestimmte Einstellung zu finden. Standardmäßig werden bei der Suche nur AOSP-Einstellungen gefunden. Zusätzliche Einstellungen, unabhängig davon, ob sie eingefügt wurden oder nicht, erfordern zusätzliche Änderungen, um indexiert zu werden.

Voraussetzungen

Damit eine Einstellung in der Einstellungssuche indexiert werden kann, müssen die Daten aus einer der folgenden Quellen stammen:

  • SearchIndexable-Fragment in CarSettings
  • App auf Systemebene.

Daten definieren

Gängige Felder:

  • Key: (Erforderlich) Eindeutiger, visuell lesbarer Stringschlüssel zur Identifizierung des Ergebnisses.
  • IconResId. Optional: Wenn in Ihrer App neben dem Ergebnis ein Symbol angezeigt wird, fügen Sie die Ressourcen-ID hinzu. Andernfalls ignorieren Sie sie.
  • IntentAction: Erforderlich, wenn IntentTargetPackage oder IntentTargetClass nicht definiert ist. Definiert die Aktion, die mit dem Suchergebnis ausgeführt werden soll.
  • IntentTargetPackage: Erforderlich, wenn IntentAction nicht definiert ist. Definiert das Paket, auf das die Suchergebnisabsicht verweisen soll.
  • IntentTargetClass: Erforderlich, wenn IntentAction nicht definiert ist. Definiert die Klasse (Aktivität), auf die die Suchergebnisabsicht zurückzuführen ist.

Nur SearchIndexableResource:

  • XmlResId: (Erforderlich) Definiert die XML-Ressourcen-ID der Seite mit den zu indexierenden Ergebnissen.

Nur SearchIndexableRaw:

  • Title: (Erforderlich) Titel des Suchergebnisses.
  • SummaryOn: (Optional) Zusammenfassung des Suchergebnisses.
  • Keywords: (Optional) Liste der Wörter, die mit dem Suchergebnis verknüpft sind. Die Abfrage wird mit dem Ergebnis abgeglichen.
  • ScreenTitle: (Optional) Titel der Seite mit dem Suchergebnis.

Daten ausblenden

Sofern nicht anders angegeben, werden alle Suchergebnisse in der Google Suche angezeigt. Während statische Suchergebnisse im Cache gespeichert werden, wird jedes Mal, wenn die Suche geöffnet wird, eine neue Liste nicht indexierbarer Schlüssel abgerufen. Mögliche Gründe für das Ausblenden von Ergebnissen:

  • Duplizieren Er wird beispielsweise auf mehreren Seiten angezeigt.
  • Wird nur bedingt angezeigt. Beispielsweise werden die Einstellungen für die mobilen Daten nur angezeigt, wenn eine SIM-Karte vorhanden ist.
  • Seite mit Vorlage Beispiel: Detailseite einer einzelnen App.
  • Die Einstellung benötigt mehr Kontext als ein Titel und eine Untertitelung. Beispiel: Eine Einstellung „Einstellungen“, die nur für den Bildschirmtitel relevant ist.

Wenn eine Einstellung ausgeblendet werden soll, muss Ihr Anbieter oder SEARCH_INDEX_DATA_PROVIDER den Schlüssel des Suchergebnisses aus getNonIndexableKeys zurückgeben. Der Schlüssel kann immer zurückgegeben (Duplikate, Seiten mit Vorlagen) oder bedingt hinzugefügt werden (kein Fall mit mobilen Daten).

Statischer Index

Verwenden Sie den statischen Index, wenn Ihre Indexdaten immer gleich sind. Dazu gehören beispielsweise der Titel und die Zusammenfassung der XML-Daten oder die Rohdaten des Hardcodes. Die statischen Daten werden nur einmal indexiert, wenn die Suche in den Einstellungen zum ersten Mal gestartet wird.

Für indexierbare Elemente in den Einstellungen implementieren Sie getXmlResourcesToIndex und/oder getRawDataToIndex. Implementieren Sie für die eingefügten Einstellungen die Methoden queryXmlResources und/oder queryRawData.

Dynamischer Index

Wenn die indexierbaren Daten entsprechend aktualisiert werden können, verwenden Sie die dynamische Methode, um Ihre Daten zu indexieren. Die Einstellungen werden bei der Einführung der Suchfunktion aktualisiert.

Implementieren Sie für indexierbare Elemente in den Einstellungen getDynamicRawDataToIndex. Implementiere für die eingefügten Einstellungen die queryDynamicRawData methods.

In den Autoeinstellungen indexieren

Informationen zum Indexieren verschiedener Einstellungen, die in die Suchfunktion einbezogen werden sollen, finden Sie unter Inhaltsanbieter. Die Pakete SettingsLib und android.provider haben bereits definierte Schnittstellen und abstrakte Klassen, die erweitert werden können, um neue Einträge für die Indexierung bereitzustellen. In den AOSP-Einstellungen werden Implementierungen dieser Klassen zum Indexieren von Ergebnissen verwendet. Die primäre Schnittstelle, die erfüllt werden muss, ist SearchIndexablesProvider. Sie wird von SettingsIntelligence zum Indexieren von Daten verwendet.

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

Theoretisch könnte jedes Fragment den Ergebnissen in SearchIndexablesProvider hinzugefügt werden. In diesem Fall wäre SettingsIntelligence ein Inhalt. Verwenden Sie die SettingsLib-Codegenerierung von SearchIndexableResources, um den Prozess möglichst einfach zu gestalten und neue Fragmente hinzuzufügen. Bei den Einstellungen für Autos ist jedes indexierbare Fragment mit @SearchIndexable annotiert und hat dann ein statisches SearchIndexProvider-Feld, das die relevanten Daten für dieses Fragment enthält. Fragmente mit der Anmerkung, aber ohne SearchIndexProvider führen zu einem Kompilierungsfehler.

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

Alle diese Fragmente werden dann der automatisch generierten SearchIndexableResourcesAuto-Klasse hinzugefügt, die eine dünne Ummantelung um die Liste der SearchIndexProvider-Felder für alle Fragmente ist. Es wird unterstützt, ein bestimmtes Ziel für eine Anmerkung anzugeben (z. B. „Auto“, „TV“ und „Wear“). Die meisten Anmerkungen bleiben jedoch bei der Standardeinstellung (All). In diesem Anwendungsfall ist es nicht erforderlich, das Ziel „Auto“ anzugeben. Es bleibt also bei All. Die Basisimplementierung von SearchIndexProvider sollte für die meisten Fragmente ausreichen.

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

Neues Fragment indexieren

Mit diesem Design ist es relativ einfach, eine neue zu indexierende SettingsFragment hinzuzufügen. In der Regel ist dafür eine Aktualisierung mit zwei Zeilen erforderlich, die die XML-Datei für das Fragment und die befolgte Absicht enthält. Beispiel für WifiSettingsFragment:

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

Die AAOS-Implementierung der SearchIndexablesProvider, die SearchIndexableResources verwendet und die Übersetzung von SearchIndexProviders in das Datenbankschema für SettingsIntelligence vornimmt, aber nicht darauf achtet, welche Fragmente indexiert werden. SettingsIntelligence unterstützt eine beliebige Anzahl von Anbietern. So können neue Anbieter für spezielle Anwendungsfälle erstellt werden, die sich auf Ergebnisse mit ähnlichen Strukturen konzentrieren. Den in der PreferenceScreen für das Fragment definierten Einstellungen muss jeweils ein eindeutiger Schlüssel zugewiesen sein, damit sie indexiert werden können. Außerdem muss PreferenceScreen ein Schlüssel zugewiesen sein, damit der Bildschirmtitel indexiert werden kann.

Indexbeispiele

In einigen Fällen ist einem Fragment möglicherweise keine bestimmte Intent-Aktion zugewiesen. In solchen Fällen können Sie die Aktivitätsklasse über eine Komponente anstelle einer Aktion an den CarBaseSearchIndexProvider-Intent übergeben. Dazu muss die Aktivität in der Manifestdatei vorhanden und exportiert werden.

@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 einigen Sonderfällen müssen einige Methoden von CarBaseSearchIndexProvider möglicherweise überschrieben werden, damit die gewünschten Ergebnisse indexiert werden. Beispielsweise sollten in NetworkAndInternetFragment Einstellungen für Mobilfunknetze nicht auf Geräten ohne Mobilfunknetz indexiert werden. Überschreiben Sie in diesem Fall die Methode getNonIndexableKeys und kennzeichnen Sie die entsprechenden Schlüssel als nicht indexierbar, wenn ein Gerät kein Mobilfunknetz hat.

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

Je nach den Anforderungen des jeweiligen Fragments können andere Methoden der CarBaseSearchIndexProvider überschrieben werden, um andere indexierbare Daten wie statische und dynamische Rohdaten einzubeziehen.

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

Einstellungen für Indexeinträge

So fügen Sie eine Einstellung ein, die indexiert werden soll:

  1. Definieren Sie eine SearchIndexablesProvider für Ihre App, indem Sie die Klasse android.provider.SearchIndexablesProvider erweitern.
  2. Aktualisieren Sie die AndroidManifest.xml der App bei dem Anbieter in Schritt 1. Das Format ist:
    <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. Fügen Sie Ihrem Anbieter indexierbare Daten hinzu. Die Implementierung hängt von den Anforderungen der App ab. Es können zwei verschiedene Datentypen indexiert werden: SearchIndexableResource und SearchIndexableRaw.

Beispiel für 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;
        }
    }
}