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 diCarSettings
. - 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 seIntentTargetPackage
oIntentTargetClass
non è definito. Definisce l'azione che deve essere eseguita dall'intento del risultato della ricerca. -
IntentTargetPackage
. Obbligatorio seIntentAction
non è definito. Definisce il pacchetto in cui deve essere risolto l'intento del risultato della ricerca. -
IntentTargetClass
. Obbligatorio seIntentAction
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:
- Definisci un
SearchIndexablesProvider
per la tua app estendendo la classeandroid.provider.SearchIndexablesProvider
. - 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>
- Aggiungi dati indicizzabili al tuo provider. L'implementazione dipende dalle esigenze dell'app. È possibile indicizzare due diversi tipi di dati:
SearchIndexableResource
eSearchIndexableRaw
.
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; } } }