Indexation de la recherche des paramètres de la voiture

La recherche de paramètres vous permet de rechercher et de modifier rapidement et facilement des paramètres spécifiques dans l'application Paramètres automobiles sans 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 trouve uniquement 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.

Exigences

Pour qu'un paramètre puisse être indexé par la recherche de paramètres, les données doivent provenir de :

  • Fragment SearchIndexable dans CarSettings .
  • Application au niveau du système.

Définir les données

Champs communs :

  • Key . ( Obligatoire ) Clé de chaîne unique lisible par l'homme pour identifier le résultat.
  • IconResId . Facultatif Si une icône apparaît dans votre application à côté du résultat, ajoutez l'ID de ressource, sinon ignorez-le.
  • IntentAction . Obligatoire si IntentTargetPackage ou IntentTargetClass n’est pas défini. Définit l’action que l’intention du résultat de recherche doit entreprendre.
  • IntentTargetPackage . Obligatoire si IntentAction n’est pas défini. Définit le package vers lequel l’intention du résultat de recherche doit être résolue.
  • IntentTargetClass . Obligatoire si IntentAction n’est pas défini. Définit la classe (activité) à laquelle l’intention du résultat de recherche doit être résolue.

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 la recherche.
  • SummaryOn . ( Facultatif ) Résumé du résultat de la recherche.
  • Keywords . ( Facultatif ) Liste des mots associés au résultat de la recherche. Fait correspondre 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 apparaît dans la recherche, sauf indication contraire. Alors que les résultats de recherche statiques sont mis en cache, une nouvelle liste de clés non indexables est récupérée à chaque fois que la recherche est ouverte. Les raisons pour lesquelles les résultats sont masqués peuvent inclure :

  • Dupliquer. Par exemple, apparaît sur plusieurs pages.
  • Affiché uniquement sous condition. Par exemple, affiche uniquement les paramètres de données mobiles lorsqu'une carte SIM est présente).
  • Page modèle. Par exemple, une page de détails pour une application individuelle.
  • Le 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 la recherche à partir de getNonIndexableKeys . La clé peut toujours être renvoyée (cas de page en double, modèle) ou ajoutée sous condition (pas de cas de données mobiles).

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 du code en dur. Les données statiques ne sont indexées qu'une seule fois lors du premier lancement de la recherche Paramètres.

Pour les 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 de paramètres met à jour cette liste dynamique lors de son lancement.

Pour les indexables dans les paramètres, implémentez getDynamicRawDataToIndex . Pour les paramètres injectés, implémentez les queryDynamicRawData methods .

Index dans les paramètres de la voiture

Pour indexer différents paramètres à inclure dans la fonction de recherche, consultez Fournisseurs de contenu . Les packages SettingsLib et android.provider ont déjà des interfaces définies 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. La principale interface à 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 pourrait être ajouté aux résultats dans SearchIndexablesProvider , auquel cas SettingsIntelligence serait contenu. Pour rendre le processus facile à maintenir et à ajouter de nouveaux fragments, utilisez la génération de code SettingsLib de SearchIndexableResources . Spécifique aux paramètres de voiture, chaque fragment indexable est annoté avec @SearchIndexable et possède ensuite un champ SearchIndexProvider statique qui fournit les données pertinentes pour ce fragment. Les fragments avec l'annotation mais manquant un SearchIndexProvider entraînent 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 mince wrapper autour de la liste des champs SearchIndexProvider pour tous les fragments. Une prise en charge est fournie pour spécifier une cible spécifique pour une annotation (telle que Auto, TV et Wear), mais la plupart des annotations sont laissées par défaut ( All ). Dans ce cas d'utilisation, il n'existe aucun besoin spécifique de spécifier la cible Auto, 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 sur deux lignes fournissant le XML du fragment et l'intention à suivre. Avec 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);
}

L'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 est indépendante des fragments qui sont indexés. SettingsIntelligence prend en charge un nombre illimité de fournisseurs, de sorte que de nouveaux fournisseurs peuvent être créés pour prendre en charge des cas d'utilisation spécialisés, permettant à chacun d'être spécialisé et de se concentrer sur des résultats avec des structures similaires. Les préférences définies dans le PreferenceScreen pour le fragment doivent chacune se voir attribuer une clé unique pour être indexées. De plus, le PreferenceScreen doit avoir une touche attribuée pour que le titre de l'écran soit indexé.

Exemples d'index

Dans certains cas, un fragment peut ne pas être associé à une action d’intention spécifique. Dans de tels cas, il est possible de transmettre la classe d'activité dans l'intention CarBaseSearchIndexProvider à l'aide d'un composant plutôt que d'une action. Cela nécessite toujours que l'activité soit présente dans le fichier manifeste et qu'elle soit 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 obtenir l'indexation des résultats souhaité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 de 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 injectés par l'index

Pour injecter un paramètre à indexer :

  1. Définissez un SearchIndexablesProvider pour votre application en étendant la classe android.provider.SearchIndexablesProvider .
  2. Mettez à jour le AndroidManifest.xml de l'application avec le fournisseur à l'étape 1. Le format est :
    <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. Ajoutez des données indexables à votre fournisseur. La mise en œuvre dépend des besoins de l'application. Deux types de données différents peuvent être indexés : SearchIndexableResource et SearchIndexableRaw .

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