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 inCarSettings
. - 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 , wennIntentTargetPackage
oderIntentTargetClass
nicht definiert ist. Definiert die Aktion, die die Suchergebnisabsicht ausführen soll. -
IntentTargetPackage
. Erforderlich , wennIntentAction
nicht definiert ist. Definiert das Paket, in das das Suchergebnis aufgelöst werden soll. -
IntentTargetClass
. Erforderlich , wennIntentAction
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:
- Definieren Sie einen
SearchIndexablesProvider
für Ihre App, indem Sie die Klasseandroid.provider.SearchIndexablesProvider
erweitern. - 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>
- 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
undSearchIndexableRaw
.
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; } } }