La ricerca nelle impostazioni ti consente di cercare e modificare rapidamente e facilmente impostazioni specifiche nell'app Impostazioni auto senza dover navigare nei menu dell'app per trovarle. La ricerca è il modo più efficace per trovare un'impostazione specifica. Per impostazione predefinita, la ricerca trova solo le impostazioni AOSP. Le impostazioni aggiuntive, iniettate o meno, richiedono modifiche aggiuntive per essere indicizzate.
Requisiti
Affinché un'impostazione sia indicizzata dalla ricerca Impostazioni, i dati devono provenire da:
- Frammento
SearchIndexable
all'interno diCarSettings
. - App a livello di sistema.
Definisci i dati
Campi comuni:
Key
. (Obbligatorio) Chiave stringa univoca leggibile per identificare il risultato.IconResId
. Facoltativo Se nella tua app viene visualizzata un'icona accanto al risultato, aggiungi l'ID risorsa, altrimenti ignora.IntentAction
. Obbligatorio seIntentTargetPackage
oIntentTargetClass
non è definito. Definisce l'azione che deve essere eseguita dall'intent del risultato di ricerca.IntentTargetPackage
. Obbligatorio seIntentAction
non è definito. Definisce il pacchetto a cui deve essere risolto l'intento del risultato di ricerca.IntentTargetClass
. Obbligatorio seIntentAction
non è definito. Definisce la classe (attività) a cui deve essere risolto 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 alla query del risultato.ScreenTitle
. (Facoltativo) Il titolo della pagina con il risultato di ricerca.
Nascondere i dati
Ogni risultato di ricerca viene visualizzato nella Ricerca, a meno che non sia indicato diversamente. Sebbene i risultati di ricerca statici vengano memorizzati nella cache, ogni volta che viene aperta la ricerca viene recuperato un elenco aggiornato delle chiavi non indicizzabili. Ecco alcuni dei motivi per cui i risultati vengono nascosti:
- Duplica. Ad esempio, viene visualizzato su più pagine.
- Mostrato solo in modo condizionale. Ad esempio, vengono mostrate solo le impostazioni dei dati mobili se è presente una scheda SIM.
- Pagina basata su modello. Ad esempio, una pagina dei dettagli di una singola app.
- L'impostazione richiede più contesto di un titolo e un sottotitolo. Ad esempio, un'impostazione "Impostazioni", pertinente solo al titolo della schermata.
Per nascondere un'impostazione, il fornitore o SEARCH_INDEX_DATA_PROVIDER
deve
restituire la chiave del risultato di ricerca da getNonIndexableKeys
. La chiave può essere sempre restituita (casi di pagine duplicate o basate su modelli) o aggiunta in modo condizionale (caso senza dati mobile).
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 non elaborati hardcoded. I dati statici vengono indicizzati una sola volta al primo avvio della ricerca nelle Impostazioni.
Per gli elementi indicizzati all'interno delle impostazioni, implementa getXmlResourcesToIndex
e/o getRawDataToIndex
. Per le impostazioni iniettate, implementa i metodi queryXmlResources
e/o queryRawData
.
Indice dinamico
Se i dati indicizzati possono essere aggiornati di conseguenza, utilizza il metodo dinamico per indicizzarli. La ricerca nelle impostazioni aggiorna questo elenco dinamico al momento del lancio.
Per gli elementi indicizzati all'interno delle impostazioni, implementa getDynamicRawDataToIndex
.
Per le impostazioni iniettate, implementa queryDynamicRawData methods
.
Indice nelle impostazioni dell'auto
Per indicizzare impostazioni diverse da includere nella funzionalità di ricerca, consulta
Provider di contenuti. I pacchetti SettingsLib
e android.provider
hanno già interfacce e classi astratte definite 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 il contenuto. Per semplificare la gestione del processo e aggiungere facilmente nuovi frammenti,
utilizza la SettingsLib
generazione di codice di SearchIndexableResources
.
In modo specifico per le impostazioni dell'auto, ogni frammento indicizzato è annotato con @SearchIndexable
e dispone di un campo SearchIndexProvider
statico che fornisce i dati pertinenti per quel frammento. I frammenti con l'annotazione, ma senza 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 poi aggiunti alla classe SearchIndexableResourcesAuto
generata automaticamente, che è un wrapper sottile intorno all'elenco dei campi SearchIndexProvider
per tutti i frammenti.
È supportata la specifica di un target specifico per un'annotazione (ad esempio auto, TV e Wear), ma la maggior parte delle annotazioni rimane impostata sul valore predefinito (All
). In questo caso d'uso, non è necessario specificare il target automatico, che rimane impostato su All
. L'implementazione di base di SearchIndexProvider
è pensata 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; } }
Indicizzare un nuovo frammento
Con questo design, è relativamente facile aggiungere un nuovo SettingsFragment
da indicizzare, in genere un aggiornamento di due righe che fornisce il codice XML per il frammento e l'intent 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
e esegue la traduzione da SearchIndexProviders
allo schema del database per SettingsIntelligence
, ma è agnostica rispetto ai frammenti sottoposti a indicizzazione. SettingsIntelligence
supporta un numero illimitato di fornitori, pertanto è possibile creare nuovi fornitori per supportare casi d'uso specializzati, in modo che ognuno sia specializzato e incentrato su risultati con strutture simili. Le preferenze definite in PreferenceScreen
per il frammento devono avere ciascuna una chiave univoca assegnata per poter essere
indicizzate. Inoltre, a PreferenceScreen
deve essere assegnata una chiave per poter indicizzare il titolo della schermata.
Esempi di indici
In alcuni casi, a un frammento potrebbe non essere associata un'azione di intente specifica. In questi casi, è possibile passare la classe dell'attività all'intent CarBaseSearchIndexProvider
utilizzando un componente anziché un'azione. Tuttavia, è necessario che l'attività sia presente nel file manifest
ed 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 eseguire l'override di alcuni metodi di CarBaseSearchIndexProvider
per ottenere l'indicizzazione dei risultati desiderati. Ad esempio, in
NetworkAndInternetFragment
, le preferenze relative alla rete mobile non dovevano essere messe in indicizzato sui dispositivi senza una rete mobile. In questo caso, sostituisci il metodo getNonIndexableKeys
e contrassegna le chiavi appropriate come non indicizzate 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, è possibile eseguire l'override di altri metodi di CarBaseSearchIndexProvider
per includere altri dati indicizzati, ad esempio 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 dell'indice iniettato
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 fornitore. L'implementazione dipende dalle esigenze dell'app. È possibile indicizzare due diversi tipi di dati:
SearchIndexableResource
eSearchIndexableRaw
.
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; } } }