Suchindizierung für Fahrzeugeinstellungen

Mit der Einstellungssuche können Sie in der App „Fahrzeugeinstellungen“ schnell und einfach nach bestimmten Einstellungen suchen und diese ändern, ohne durch App-Menüs navigieren zu müssen, um sie zu finden. Die Suche ist die effektivste Möglichkeit, eine bestimmte Einstellung zu finden. Standardmäßig werden bei der Suche nur AOSP-Einstellungen gefunden. Zusätzliche Einstellungen, ob injiziert oder nicht, erfordern zusätzliche Änderungen, um indiziert zu werden.

Anforderungen

Damit eine Einstellung durch die Einstellungssuche indiziert werden kann, müssen die Daten von Folgendem stammen:

  • SearchIndexable Fragment in CarSettings .
  • App auf Systemebene.

Definieren Sie die Daten

Gemeinsame Bereiche:

  • Key . ( Erforderlich ) Eindeutiger, für Menschen lesbarer String-Schlü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 es.
  • IntentAction . Erforderlich , wenn IntentTargetPackage oder IntentTargetClass nicht definiert ist. Definiert die Aktion, die die Suchergebnisabsicht ausführen soll.
  • IntentTargetPackage . Erforderlich , wenn IntentAction nicht definiert ist. Definiert das Paket, in das das Suchergebnis aufgelöst werden soll.
  • IntentTargetClass . Erforderlich , wenn IntentAction nicht definiert ist. Definiert die Klasse (Aktivität), in die das Suchergebnis aufgelöst werden soll.

Nur SearchIndexableResource :

  • XmlResId . ( Erforderlich ) Definiert die XML-Ressourcen-ID der Seite, die die zu indizierenden Ergebnisse enthält.

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. Ordnet die Abfrage Ihrem Ergebnis zu.
  • ScreenTitle . ( Optional ) Titel der Seite mit Ihrem Suchergebnis.

Daten ausblenden

Jedes Suchergebnis wird in der Suche angezeigt, sofern es nicht anders markiert ist. Während statische Suchergebnisse zwischengespeichert werden, wird bei jedem Öffnen der Suche eine neue Liste nicht indizierbarer Schlüssel abgerufen. Gründe für das Ausblenden von Ergebnissen können sein:

  • Duplikat. Erscheint beispielsweise auf mehreren Seiten.
  • Wird nur bedingt angezeigt. Zeigt beispielsweise die mobilen Dateneinstellungen nur an, wenn eine SIM-Karte vorhanden ist.
  • Mit Vorlagen versehene Seite. Zum Beispiel eine Detailseite für eine einzelne App.
  • Die Einstellung erfordert mehr Kontext als ein Titel und ein Untertitel. Beispielsweise eine „Einstellungen“-Einstellung, die nur für den Bildschirmtitel relevant ist.

Um eine Einstellung auszublenden, sollte Ihr Anbieter oder SEARCH_INDEX_DATA_PROVIDER den Schlüssel des Suchergebnisses von getNonIndexableKeys zurückgeben. Der Schlüssel kann jederzeit zurückgegeben (doppelte, mit Vorlagen versehene Seitenfälle) oder bedingt hinzugefügt werden (kein mobiler Datenfall).

Statischer Index

Verwenden Sie den statischen Index, wenn Ihre Indexdaten immer gleich sind. Zum Beispiel der Titel und die Zusammenfassung von XML-Daten oder die Hardcode-Rohdaten. Die statischen Daten werden nur einmal indiziert, wenn die Einstellungssuche zum ersten Mal gestartet wird.

Für indexierbare Elemente innerhalb der Einstellungen implementieren Sie getXmlResourcesToIndex und/oder getRawDataToIndex . Implementieren Sie für injizierte Einstellungen die Methoden queryXmlResources und/oder queryRawData .

Dynamischer Index

Wenn die indizierbaren Daten entsprechend aktualisiert werden können, verwenden Sie die dynamische Methode zur Indizierung Ihrer Daten. Die Einstellungssuche aktualisiert diese dynamische Liste, wenn sie gestartet wird.

Für indizierbare Elemente innerhalb der Einstellungen implementieren Sie getDynamicRawDataToIndex . Implementieren Sie für injizierte Einstellungen die queryDynamicRawData methods .

Index in den Fahrzeugeinstellungen

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

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 zu den Ergebnissen in SearchIndexablesProvider hinzugefügt werden. In diesem Fall wäre SettingsIntelligence Inhalt. Um die Wartung des Prozesses zum einfachen Hinzufügen neuer Fragmente zu vereinfachen, verwenden Sie SettingsLib Codegenerierung von SearchIndexableResources . Speziell für die Fahrzeugeinstellungen wird jedes indizierbare Fragment mit @SearchIndexable annotiert und verfügt dann über ein statisches SearchIndexProvider Feld, das die relevanten Daten für dieses Fragment bereitstellt. Fragmente mit der Annotation, denen jedoch ein SearchIndexProvider fehlt, 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 ein dünner Wrapper um die Liste der SearchIndexProvider Felder für alle Fragmente ist. Es wird Unterstützung für die Angabe eines bestimmten Ziels für eine Anmerkung bereitgestellt (z. B. „Auto“, „TV“ und „Verschleiß“). Die meisten Anmerkungen bleiben jedoch auf der Standardeinstellung ( All ). In diesem Anwendungsfall besteht keine besondere Notwendigkeit, das Auto-Ziel anzugeben, daher bleibt es bei All . Die Basisimplementierung von SearchIndexProvider soll für die meisten Fragmente ausreichend sein.

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

Indizieren Sie ein neues Fragment

Mit diesem Design ist es relativ einfach, ein neues SettingsFragment zur Indizierung hinzuzufügen, normalerweise eine zweizeilige Aktualisierung, die den XML-Code für das Fragment und die zu verfolgende Absicht bereitstellt. Am Beispiel von 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 von SearchIndexablesProvider , die SearchIndexableResources verwendet und die Übersetzung von SearchIndexProviders in das Datenbankschema für SettingsIntelligence durchführt, jedoch unabhängig davon ist, welche Fragmente indiziert werden. SettingsIntelligence unterstützt eine beliebige Anzahl von Anbietern, sodass neue Anbieter erstellt werden können, um spezielle Anwendungsfälle zu unterstützen, sodass sich jeder spezialisieren und auf Ergebnisse mit ähnlichen Strukturen konzentrieren kann. Den im PreferenceScreen für das Fragment definierten Präferenzen muss jeweils ein eindeutiger Schlüssel zugewiesen sein, damit sie indiziert werden können. Darüber hinaus muss dem PreferenceScreen eine Taste zugewiesen sein, damit der Bildschirmtitel indiziert werden kann.

Indexbeispiele

In einigen Fällen ist einem Fragment möglicherweise keine bestimmte Absichtsaktion zugeordnet. In solchen Fällen ist es möglich, die Aktivitätsklasse mithilfe einer Komponente anstelle einer Aktion an die Absicht CarBaseSearchIndexProvider “ zu übergeben. Dies erfordert weiterhin, dass die Aktivität in der Manifestdatei vorhanden ist und exportiert werden kann.

@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 möglicherweise einige Methoden von CarBaseSearchIndexProvider überschrieben werden, um die gewünschten zu indizierenden Ergebnisse zu erhalten. Beispielsweise sollten in NetworkAndInternetFragment Präferenzen im Zusammenhang mit dem Mobilfunknetz nicht auf Geräten ohne Mobilfunknetz indiziert werden. Überschreiben Sie in diesem Fall die Methode getNonIndexableKeys und markieren Sie die entsprechenden Schlüssel als nicht indizierbar, wenn ein Gerät nicht über ein Mobilfunknetz verfügt.

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

Abhängig von den Anforderungen des jeweiligen Fragments können andere Methoden des CarBaseSearchIndexProvider überschrieben werden, um andere indizierbare Daten einzubeziehen, z. B. statische und dynamische Rohdaten.

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

Index-injizierte Einstellungen

So fügen Sie eine zu indizierende Einstellung ein:

  1. Definieren Sie einen SearchIndexablesProvider für Ihre App, indem Sie die Klasse android.provider.SearchIndexablesProvider erweitern.
  2. Aktualisieren Sie die AndroidManifest.xml der App mit 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 indiziert werden: SearchIndexableResource und SearchIndexableRaw .

SearchIndexablesProvider-Beispiel

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