Indeksowanie wyszukiwania ustawień samochodu

Wyszukiwanie ustawień umożliwia szybkie i łatwe wyszukiwanie oraz zmienianie konkretnych ustawień w aplikacji Ustawienia samochodu bez konieczności przechodzenia przez menu aplikacji. Wyszukiwarka to najskuteczniejszy sposób znajdowania konkretnego ustawienia. Domyślnie wyszukiwanie znajduje tylko ustawienia AOSP. Dodatkowe ustawienia, niezależnie od tego, czy zostały wstrzyknięte, wymagają wprowadzenia dodatkowych zmian, aby mogły zostać zindeksowane.

Wymagania

Aby ustawienie było indeksowane przez wyszukiwanie w ustawieniach, dane muszą pochodzić z:

  • Fragment SearchIndexable wewnątrz CarSettings.
  • Aplikacja na poziomie systemu.

Definiowanie danych

Wspólne pola:

  • Key. (Wymagany) Unikalny czytelny dla człowieka klucz tekstowy służący do identyfikowania wyniku.
  • IconResId. Opcjonalnie jeśli w aplikacji obok wyniku wyświetla się ikona, dodaj identyfikator zasobu. W przeciwnym razie zignoruj tę opcję.
  • IntentAction. Wymagane, jeśli nie zdefiniowano wartości IntentTargetPackage ani IntentTargetClass. Określa działanie, które ma wykonać intencja dotycząca wyniku wyszukiwania.
  • IntentTargetPackage. Wymagany, jeśli nie zdefiniowano IntentAction. Określa pakiet, który ma być rozwiązywany przez intencję wyniku wyszukiwania.
  • IntentTargetClass. Wymagany, jeśli nie zdefiniowano IntentAction. Określa klasę (czynność), do której ma być rozwiązany zamiar w wyniku wyszukiwania.

Tylko SearchIndexableResource:

  • XmlResId. (Wymagany) Określa identyfikator zasobu XML strony zawierającej wyniki, które mają zostać zindeksowane.

Tylko SearchIndexableRaw:

  • Title. (Wymagany) Tytuł wyniku wyszukiwania.
  • SummaryOn. (Opcjonalnie) Podsumowanie wyników wyszukiwania.
  • Keywords. (Opcjonalnie) Lista słów powiązanych z wynikiem wyszukiwania. Dopasowuje zapytanie do wyniku.
  • ScreenTitle. (Opcjonalnie) Tytuł strony z wynikiem wyszukiwania.

Ukrywanie danych

Każdy wynik wyszukiwania pojawia się w wyszukiwarce, chyba że jest inaczej oznaczony. Chociaż wyniki wyszukiwania statycznych zapytań są przechowywane w pamięci podręcznej, za każdym razem, gdy otwierasz wyszukiwanie, pobierana jest nowa lista kluczy, których nie można zindeksować. Wyniki mogą być ukryte z tych powodów:

  • Duplikat. na przykład pojawia się na wielu stronach.
  • Wyświetlane tylko warunkowo. Na przykład tylko wtedy, gdy jest obecna karta SIM, wyświetla ustawienia danych mobilnych.
  • Strona na podstawie szablonu Może to być na przykład strona z informacjami o pojedynczej aplikacji.
  • Ustawienie wymaga więcej kontekstu niż tylko tytuł i podtytuł. Na przykład ustawienie „Ustawienia” jest istotne tylko w przypadku tytułu ekranu.

Aby ukryć ustawienie, dostawca lub SEARCH_INDEX_DATA_PROVIDER powinien zwrócić klucz wyników wyszukiwania z getNonIndexableKeys. Klucz może być zwracany zawsze (w przypadku duplikatów i stron opartych na szablonach) lub warunkowo dodawany (w przypadku braku danych mobilnych).

Indeks statyczny

Użyj indeksu statycznego, jeśli dane indeksu są zawsze takie same. Może to być np. tytuł i podsumowanie danych XML lub nieprzetworzone dane zakodowane na stałe. Dane statyczne są indeksowane tylko raz, gdy wyszukiwanie w Ustawieniach zostanie uruchomione po raz pierwszy.

W przypadku elementów indeksowanych w ustawieniach zastosuj getXmlResourcesToIndex lub getRawDataToIndex. W przypadku wstrzykiwanych ustawień zaimplementuj metody queryXmlResources lub queryRawData.

Indeks dynamiczny

Jeśli dane podlegające indeksowaniu można odpowiednio zaktualizować, użyj metody dynamicznej do ich zindeksowania. Wyszukiwanie w ustawieniach aktualizuje tę listę dynamicznie po jej uruchomieniu.

W przypadku indeksowania w ustawieniach zastosuj getDynamicRawDataToIndex. W przypadku wstrzykiwanych ustawień zastosuj queryDynamicRawData methods.

Indeks w Ustawieniach samochodu

Aby indeksować różne ustawienia, które mają być uwzględniane w funkcji wyszukiwania, zapoznaj się z artykułem Dostawcy treści. Pakiety SettingsLibandroid.provider mają już zdefiniowane interfejsy i klasy abstrakcyjne, które można rozszerzać, aby udostępniać nowe wpisy do zindeksowania. W ustawieniach AOSP implementacje tych klas są używane do indeksowania wyników. Głównym interfejsem, który należy wypełnić, jest SearchIndexablesProvider, który jest używany przez SettingsIntelligence do indeksowania danych.

public abstract class SearchIndexablesProvider extends ContentProvider {
    public abstract Cursor queryXmlResources(String[] projection);
    public abstract Cursor queryRawData(String[] projection);
    public abstract Cursor queryNonIndexableKeys(String[] projection);
}

Teoretycznie każdy fragment może zostać dodany do wyników w SearchIndexablesProvider, w którym przypadku SettingsIntelligencebędzie zawierać treść. Aby ułatwić sobie dodawanie nowych fragmentów, użyj funkcji SettingsLib do generowania kodu SearchIndexableResources. W przypadku ustawień samochodu każdy fragment, który można zindeksować, jest opatrzony adnotacją @SearchIndexable, a następnie ma statyczne pole SearchIndexProvider, które zawiera odpowiednie dane dotyczące tego fragmentu. Fragmenty z adnotacją , ale bez SearchIndexProvider powodują błąd kompilacji.

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

Wszystkie te fragmenty są następnie dodawane do automatycznie wygenerowanej klasy SearchIndexableResourcesAuto, która jest cienką osłonką wokół listy pól SearchIndexProvider dla wszystkich fragmentów. Dotyczy to określania konkretnego celu adnotacji (np. Auto, TV i Wear), ale większość adnotacji pozostaje domyślna (All). W tym przypadku nie ma potrzeby określania celu Auto, więc pozostaje on ustawiony jako All. Podstawowa implementacja SearchIndexProvider powinna wystarczyć w przypadku większości fragmentów.

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

Indeksowanie nowego fragmentu

Dzięki temu rozwiązaniu można stosunkowo łatwo dodać nowy SettingsFragment do zindeksowania. Zwykle jest to dwuwierszowe zaktualizowanie kodu XML fragmentu i intencji, której ma on dotyczyć. Na przykład: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);
}

Implementacja SearchIndexablesProvider w AAOS, która korzysta z SearchIndexableResources i przekształca SearchIndexProviders w schemat bazy danych dla SettingsIntelligence, ale nie ma znaczenia, jakie fragmenty są indeksowane. SettingsIntelligence obsługuje dowolną liczbę dostawców, więc można tworzyć nowych dostawców do obsługi wyspecjalizowanych przypadków użycia, aby każdy z nich był wyspecjalizowany i skoncentrowany na uzyskiwaniu wyników w podobnych strukturach. Aby można było je zindeksować, preferencje zdefiniowane w PreferenceScreen dla fragmentu muszą mieć przypisane unikalne klucze. Dodatkowo PreferenceScreen musi mieć przypisany klucz, aby można było zindeksować tytuł ekranu.

Przykłady indeksów

W niektórych przypadkach fragment może nie mieć określonego działania związanego z zamiarem. W takich przypadkach można przekazać klasę aktywności do intencji CarBaseSearchIndexProvider za pomocą komponentu zamiast działania. Nadal wymaga to obecności aktywności w pliku manifestu i jej wyeksportowania.

@SearchIndexable
public class LanguagesAndInputFragment extends SettingsFragment {
[...]
    public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
        new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment,
                LanguagesAndInputActivity.class);
}

W niektórych szczególnych przypadkach może być konieczne zastąpienie niektórych metod CarBaseSearchIndexProvider, aby uzyskać pożądane wyniki indeksowania. Na przykład w NetworkAndInternetFragment preferencje dotyczące sieci komórkowej nie były indeksowane na urządzeniach bez sieci komórkowej. W takim przypadku zastąpij metodę getNonIndexableKeys i oznacz odpowiednie klucze jako nieindeksowane, gdy urządzenie nie ma dostępu do sieci komórkowej.

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

W zależności od potrzeb konkretnego fragmentu inne metody CarBaseSearchIndexProvider mogą zostać zastąpione, aby uwzględnić inne dane podlegające indeksowaniu, takie jak dane nieprzetworzone statyczne i dynamiczne.

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

Ustawienia indeksu

Aby wstrzyknąć ustawienie do indeksowania:

  1. Określ SearchIndexablesProvider dla swojej aplikacji, rozszerzając klasę android.provider.SearchIndexablesProvider.
  2. Zaktualizuj w aplikacji AndroidManifest.xml informacje o dostawcy podane w kroku 1. Format:
    <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. Dodaj do dostawcy dane, które można zindeksować. Implementacja zależy od potrzeb aplikacji. Można indeksować 2 typy danych: SearchIndexableResourceSearchIndexableRaw.

Przykład 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;
        }
    }
}