La recherche dans les paramètres vous permet de rechercher et de modifier rapidement et facilement des paramètres spécifiques dans l'application Paramètres automobiles, sans avoir à naviguer dans les menus de l'application pour les trouver. La recherche est le moyen le plus efficace de trouver un paramètre spécifique. Par défaut, la recherche ne trouve que les paramètres AOSP. Les paramètres supplémentaires, qu'ils soient injectés ou non, nécessitent des modifications supplémentaires pour être indexés.
Conditions requises
Pour qu'un paramètre puisse être indexé par la recherche dans les paramètres, les données doivent provenir d'un:
- Fragment
SearchIndexable
dansCarSettings
. - Application au niveau du système.
Définir les données
Champs courants:
Key
(obligatoire) Clé de chaîne unique lisible par l'utilisateur pour identifier le résultat.IconResId
. Facultatif : si une icône s'affiche dans votre application à côté du résultat, ajoutez l'ID de la ressource, sinon ignorez-la.IntentAction
. Obligatoire siIntentTargetPackage
ouIntentTargetClass
n'est pas défini. Définit l'action à effectuer par l'intent de résultat de recherche.IntentTargetPackage
. Obligatoire siIntentAction
n'est pas défini. Définit le package auquel l'intent de résultat de recherche doit se résoudre.IntentTargetClass
. Obligatoire siIntentAction
n'est pas défini. Définit la classe (activité) à laquelle l'intent de résultat de recherche doit être résolu.
SearchIndexableResource
uniquement:
XmlResId
(obligatoire) : définit l'ID de ressource XML de la page contenant les résultats à indexer.
SearchIndexableRaw
uniquement:
Title
(obligatoire) : titre du résultat de recherche.SummaryOn
(facultatif) : résumé du résultat de recherche.Keywords
(facultatif) : liste des mots associés au résultat de recherche. Associe la requête à votre résultat.ScreenTitle
(facultatif) : titre de la page contenant votre résultat de recherche.
Masquer les données
Chaque résultat de recherche s'affiche dans la recherche, sauf si vous le marquez comme tel. Bien que les résultats de recherche statiques soient mis en cache, une nouvelle liste de clés non indexables est récupérée chaque fois qu'une recherche est ouverte. Voici quelques raisons pour lesquelles vous pouvez masquer des résultats:
- Dupliquer. (par exemple, s'affiche sur plusieurs pages) ;
- Affiché uniquement de manière conditionnelle. (par exemple, n'affiche les paramètres des données mobiles que lorsqu'une carte SIM est présente).
- Page modélisée. Par exemple, une page d'informations sur une application spécifique.
- Un paramètre nécessite plus de contexte qu'un titre et un sous-titre. Par exemple, un paramètre "Paramètres", qui ne concerne que le titre de l'écran.
Pour masquer un paramètre, votre fournisseur ou SEARCH_INDEX_DATA_PROVIDER
doit renvoyer la clé du résultat de recherche à partir de getNonIndexableKeys
. La clé peut toujours être renvoyée (cas de pages en double ou avec modèle) ou ajoutée de manière conditionnelle (cas de données mobiles manquantes).
Indice statique
Utilisez l'index statique si vos données d'index sont toujours les mêmes. Par exemple, le titre et le résumé des données XML ou les données brutes codées en dur. Les données statiques ne sont indexées qu'une seule fois lors du premier lancement de la recherche dans les paramètres.
Pour les éléments indexables dans les paramètres, implémentez getXmlResourcesToIndex
et/ou getRawDataToIndex
. Pour les paramètres injectés, implémentez les méthodes queryXmlResources
et/ou queryRawData
.
Indice dynamique
Si les données indexables peuvent être mises à jour en conséquence, utilisez la méthode dynamique pour indexer vos données. La recherche dans les paramètres met à jour cette liste dynamique lorsqu'elle est lancée.
Pour les éléments indexables dans les paramètres, implémentez getDynamicRawDataToIndex
.
Pour les paramètres injectés, implémentez queryDynamicRawData methods
.
Index dans les paramètres du véhicule
Pour indexer différents paramètres à inclure dans la fonctionnalité de recherche, consultez la section Fournisseurs de contenu. Les packages SettingsLib
et android.provider
ont déjà défini des interfaces et des classes abstraites à étendre pour fournir de nouvelles entrées à indexer. Dans les paramètres AOSP, les implémentations de ces classes sont utilisées pour indexer les résultats. L'interface principale à remplir est SearchIndexablesProvider
, qui est utilisée par SettingsIntelligence
pour indexer les données.
public abstract class SearchIndexablesProvider extends ContentProvider { public abstract Cursor queryXmlResources(String[] projection); public abstract Cursor queryRawData(String[] projection); public abstract Cursor queryNonIndexableKeys(String[] projection); }
En théorie, chaque fragment peut être ajouté aux résultats dans SearchIndexablesProvider
, auquel cas SettingsIntelligence
serait le contenu. Pour faciliter la gestion du processus et ajouter facilement de nouveaux fragments, utilisez la génération de code SettingsLib
de SearchIndexableResources
.
Spécifique aux paramètres de la voiture, chaque fragment indexable est annoté avec @SearchIndexable
, puis comporte un champ SearchIndexProvider
statique qui fournit les données pertinentes pour ce fragment. Les fragments avec l'annotation, mais sans SearchIndexProvider
, génèrent une erreur de compilation.
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); }
Tous ces fragments sont ensuite ajoutés à la classe SearchIndexableResourcesAuto
générée automatiquement, qui est un wrapper mince autour de la liste des champs SearchIndexProvider
pour tous les fragments.
Il est possible de spécifier une cible spécifique pour une annotation (par exemple, Auto, TV et Wear), mais la plupart des annotations restent par défaut (All
). Dans ce cas d'utilisation, il n'est pas nécessaire de spécifier la cible automatique. Elle reste donc All
. L'implémentation de base de SearchIndexProvider
est censée être suffisante pour la plupart des fragments.
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; } }
Indexer un nouveau fragment
Avec cette conception, il est relativement facile d'ajouter un nouveau SettingsFragment
à indexer, généralement une mise à jour en deux lignes fournissant le XML du fragment et l'intent à suivre. Prenons WifiSettingsFragment
comme exemple:
@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); }
Implémentation AAOS de SearchIndexablesProvider
, qui utilise SearchIndexableResources
et effectue la traduction de SearchIndexProviders
dans le schéma de base de données pour SettingsIntelligence
, mais qui est indépendante des fragments indexés. SettingsIntelligence
est compatible avec un nombre illimité de fournisseurs. Vous pouvez donc en créer de nouveaux pour prendre en charge des cas d'utilisation spécialisés, ce qui leur permet d'être spécialisés et axés sur les résultats avec des structures similaires. Une clé unique doit être attribuée à chacune des préférences définies dans le PreferenceScreen
pour le fragment afin qu'elles soient indexées. De plus, une clé doit être attribuée à PreferenceScreen
pour que le titre de l'écran soit indexé.
Exemples d'index
Dans certains cas, aucune action d'intent spécifique n'est associée à un fragment. Dans ce cas, vous pouvez transmettre la classe d'activité à l'intent CarBaseSearchIndexProvider
à l'aide d'un composant plutôt que d'une action. Pour ce faire, l'activité doit toujours être présente dans le fichier manifeste et exportée.
@SearchIndexable public class LanguagesAndInputFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment, LanguagesAndInputActivity.class); }
Dans certains cas particuliers, certaines méthodes de CarBaseSearchIndexProvider
peuvent devoir être remplacées pour que les résultats souhaités soient indexés. Par exemple, dans NetworkAndInternetFragment
, les préférences liées au réseau mobile ne devaient pas être indexées sur les appareils sans réseau mobile. Dans ce cas, remplacez la méthode getNonIndexableKeys
et marquez les clés appropriées comme non indexables lorsqu'un appareil ne dispose pas d'un réseau 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; } }; }
En fonction des besoins du fragment particulier, d'autres méthodes de CarBaseSearchIndexProvider
peuvent être remplacées pour inclure d'autres données indexables, telles que des données brutes statiques et dynamiques.
@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; } }; }
Paramètres d'index injectés
Pour injecter un paramètre à indexer:
- Définissez un
SearchIndexablesProvider
pour votre application en étendant la classeandroid.provider.SearchIndexablesProvider
. - Mettez à jour le
AndroidManifest.xml
de l'application avec le fournisseur à l'étape 1. Le format est le suivant:<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>
-
Ajoutez des données indexables à votre fournisseur. L'implémentation dépend des besoins de l'application. Deux types de données différents peuvent être indexés :
SearchIndexableResource
etSearchIndexableRaw
.
Exemple de 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; } } }