Indeksowanie wyszukiwania w Ustawieniach samochodu

Wyszukiwanie ustawień pozwala szybko i łatwo znaleźć i zmienić określone ustawienia ustawieniach w sekcji Automotive Settings (Ustawienia samochodu) bez konieczności przechodzenia do menu ją znaleźć. Wyszukiwarka to najskuteczniejszy sposób znajdowania konkretnych ustawień. Domyślnie wyszukiwanie znajduje tylko ustawienia AOSP. Dodatkowe ustawienia, niezależnie od tego, czy są wstrzykiwane, czy nie wymagają dodatkowych zmian w celu zindeksowania.

Wymagania

Aby ustawienie było możliwe do zindeksowania przez wyszukiwanie w ustawieniach, dane muszą pochodzić z:

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

Zdefiniuj dane

Wspólne pola:

  • Key (Wymagany) Unikalny, zrozumiały dla człowieka klucz ciągu tekstowego do identyfikacji wyniku.
  • IconResId Opcjonalnie Jeśli w aplikacji obok ikony wynik, a następnie dodaj identyfikator zasobu. W przeciwnym razie zignoruj.
  • IntentAction. Wymagane, jeśli nie zdefiniowano właściwości IntentTargetPackage lub IntentTargetClass. Definiuje działanie wykonywane w wyniku wyszukiwania podejmujemy działania.
  • IntentTargetPackage. Wymagane, jeśli IntentAction nie jest zdefiniowane. Definiuje pakiet, z którym mają być kierowane wyniki wyszukiwania.
  • IntentTargetClass Wymagany, jeśli IntentAction nie zawiera wartości zdefiniowano jego definicję. Definiuje klasę (aktywność), do której ma przejść intencja 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 wyniku 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 zaznaczono inaczej. Statyczny wyniki wyszukiwania są zapisywane w pamięci podręcznej, za każdym razem pobierana jest nowa lista kluczy niemożliwych do zindeksowania wyszukiwanie jest otwarte. Możliwe przyczyny ukrywania wyników:

  • Duplikat. na przykład pojawia się na wielu stronach.
  • Wyświetlane tylko warunkowo. Na przykład tylko wtedy, gdy w urządzeniu jest karta SIM, wyświetlają się ustawienia transmisji danych.
  • Strona na podstawie szablonu Może to być np. strona z informacjami o konkretnej aplikacji.
  • Ustawienie wymaga więcej kontekstu niż tytułu i napisów. 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 wyniku 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. Na przykład tytuł oraz podsumowania danych w formacie XML lub nieprzetworzonych danych na stałe. Dane statyczne zostaną zindeksowane. tylko raz po uruchomieniu wyszukiwania ustawień.

W przypadku elementów możliwych do zindeksowania w ustawieniach zaimplementuj getXmlResourcesToIndex lub getRawDataToIndex. Do wstrzykiwanych ustawień zaimplementuj funkcję Metody queryXmlResources lub queryRawData.

Indeks dynamiczny

Jeśli dane możliwe do zindeksowania można odpowiednio zaktualizować, użyj metody dynamicznej do indeksowanie Twoich danych. Wyszukiwarka ustawień będzie aktualizować tę dynamiczną listę po jej uruchomieniu.

W przypadku elementów możliwych do zindeksowania w ustawieniach zaimplementuj getDynamicRawDataToIndex. W przypadku wstrzykiwanych ustawień zastosuj queryDynamicRawData methods.

Indeks w ustawieniach samochodu

Aby zindeksować różne ustawienia w celu uwzględniania ich w funkcji wyszukiwania, zapoznaj się z sekcją Treść dostawców usług. SettingsLib i android.provider pakiety mają już zdefiniowane interfejsy i klasy abstrakcyjne, które można rozszerzyć przez dostarczanie nowych wpisów do indeksowania. W ustawieniach AOSP implementacje tych 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ć proces dodawania nowych fragmentów, użyj generowania kodu za pomocą SettingsLib dla SearchIndexableResources. W ustawieniach samochodu, każdy fragment, który można zindeksować, jest opatrzony adnotacją @SearchIndexable, a następnie zawiera statyczny element typu SearchIndexProvider , które zawiera odpowiednie dane dla 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

W przypadku tego układu łatwo jest dodać nowy SettingsFragment do zindeksowania, zwykle dwuwierszową aktualizację zawierającą kod XML dla fragmentu i tag który chcesz śledzić. Na przykład:

@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 SettingsIntelligence, ale nie jest zależna od tego, które fragmenty są indeksowane. SettingsIntelligence obsługuje dowolną liczbę dostawców usług specjalistycznych, co pozwala tworzyć nowych dostawców dzięki czemu każda z nich będzie wyspecjalizowana i koncentrowała się na wynikach w różnych strukturach. Aby można było je zindeksować, preferencje zdefiniowane w PreferenceScreen dla fragmentu muszą mieć przypisane unikalne klucze. Aby indeksować tytuł ekranu, musisz też przypisać do PreferenceScreen klucz.

Przykłady indeksu

W niektórych przypadkach fragment może nie być powiązany z określonym działaniem związanym z zamiarem. W takich przypadkach można przekazać klasę aktywności do intencji CarBaseSearchIndexProvider za pomocą komponentu zamiast działania. Mimo to aktywność musi znajdować się w pliku manifestu i eksportowania.

@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 pewnych szczególnych przypadkach niektóre metody CarBaseSearchIndexProvider może wymagać zastąpienia, aby wyniki zostały zindeksowane. 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ąp getNonIndexableKeys i oznacz odpowiednie klucze jako nie można zindeksować, gdy urządzenie nie jest połączone z siecią komórkową.

@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 danego fragmentu, inne metody Pole CarBaseSearchIndexProvider może zostać zastąpione, aby uwzględnić inne danych, które można indeksować, takich jak statyczne i dynamiczne dane nieprzetworzone.

@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 wstrzykiwanego indeksu

Aby wstrzyknąć ustawienie do indeksowania:

  1. Określ SearchIndexablesProvider dla swojej aplikacji, rozszerzając android.provider.SearchIndexablesProvider zajęcia.
  2. Zaktualizuj uprawnienie AndroidManifest.xml aplikacji, podając dostawcę w kroku 1. Format to:
    <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 swojego 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;
        }
    }
}