Indeksowanie wyszukiwania ustawień samochodu

Wyszukiwanie ustawień umożliwia szybkie i łatwe wyszukiwanie i zmianę określonych ustawień w aplikacji Ustawienia samochodowe bez konieczności przechodzenia przez menu aplikacji w celu ich znalezienia. Wyszukiwanie to najskuteczniejszy sposób znalezienia określonego ustawienia. Domyślnie wyszukiwanie znajduje tylko ustawienia AOSP. Dodatkowe ustawienia, wprowadzone lub nie, wymagają dodatkowych zmian, aby mogły zostać zaindeksowane.

Wymagania

Aby ustawienie mogło być indeksowane przez wyszukiwanie ustawień, dane muszą pochodzić z:

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

Zdefiniuj dane

Wspólne pola:

  • Key . ( Wymagane ) Unikalny, czytelny dla człowieka klucz ciągu umożliwiający identyfikację wyniku.
  • IconResId . Opcjonalnie Jeśli w Twojej aplikacji obok wyniku pojawi się ikona, dodaj identyfikator zasobu, w przeciwnym razie zignoruj.
  • IntentAction . Wymagane , jeśli nie zdefiniowano IntentTargetPackage lub IntentTargetClass . Określa akcję, jaką ma podjąć intencja wyniku wyszukiwania.
  • IntentTargetPackage . Wymagane , jeśli IntentAction nie jest zdefiniowane. Definiuje pakiet, do którego ma zostać rozstrzygnięty cel wyniku wyszukiwania.
  • IntentTargetClass . Wymagane , jeśli IntentAction nie jest zdefiniowane. Definiuje klasę (działanie), do której ma zostać przeniesiony cel wyniku wyszukiwania.

Tylko SearchIndexableResource :

  • XmlResId . ( Wymagane ) Określa identyfikator zasobu XML strony zawierającej wyniki do zaindeksowania.

SearchIndexableRaw tylko indeksowany surowiec:

  • Title . ( Wymagane ) 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.

Ukryj dane

Każdy wynik wyszukiwania pojawia się w wyszukiwarce, chyba że zaznaczono inaczej. Podczas gdy statyczne wyniki wyszukiwania są buforowane, przy każdym otwarciu wyszukiwania pobierana jest nowa lista kluczy nieindeksowalnych. Przyczyny ukrywania wyników mogą obejmować:

  • Duplikować. Na przykład pojawia się na wielu stronach.
  • Wyświetlane tylko warunkowo. Na przykład pokazuje ustawienia danych mobilnych tylko wtedy, gdy włożona jest karta SIM).
  • Strona z szablonem. Na przykład strona ze szczegółami pojedynczej aplikacji.
  • Ustawienie wymaga więcej kontekstu niż tytuł i podtytuł. Na przykład ustawienie „Ustawienia”, które dotyczy tylko tytułu ekranu.

Aby ukryć ustawienie, Twój dostawca lub SEARCH_INDEX_DATA_PROVIDER powinien zwrócić klucz wyniku wyszukiwania z getNonIndexableKeys . Klucz zawsze można zwrócić (duplikat, szablon strony) lub dodać warunkowo (bez mobilnej transmisji danych).

Indeks statyczny

Użyj indeksu statycznego, jeśli dane indeksu są zawsze takie same. Na przykład tytuł i podsumowanie danych XML lub nieprzetworzone dane w kodzie. Dane statyczne są indeksowane tylko raz przy pierwszym uruchomieniu wyszukiwania ustawień.

W przypadku ustawień indeksowanych wewnątrz ustawień zaimplementuj getXmlResourcesToIndex i/lub getRawDataToIndex . W przypadku wstrzykniętych ustawień zaimplementuj metody queryXmlResources i/lub queryRawData .

Indeks dynamiczny

Jeśli dane podlegające indeksowaniu można odpowiednio zaktualizować, użyj metody dynamicznej do indeksowania danych. Wyszukiwanie ustawień aktualizuje tę dynamiczną listę po jej uruchomieniu.

W przypadku ustawień indeksowanych wewnątrz zaimplementuj getDynamicRawDataToIndex . W przypadku wstrzykniętych ustawień zaimplementuj queryDynamicRawData methods .

Indeks w ustawieniach samochodu

Aby zaindeksować różne ustawienia, które mają zostać uwzględnione w funkcji wyszukiwania, zobacz Dostawcy treści . Pakiety SettingsLib i android.provider mają już zdefiniowane interfejsy i klasy abstrakcyjne, które można rozszerzyć w celu zapewnienia nowych wpisów do indeksowania. W ustawieniach AOSP implementacje tych klas służą do indeksowania wyników. Podstawowym interfejsem, który należy speł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żna dodać do wyników w SearchIndexablesProvider , w takim przypadku SettingsIntelligence będzie treścią. Aby proces był łatwy w utrzymaniu i łatwy w dodawaniu nowych fragmentów, użyj generowania kodu SettingsLib z SearchIndexableResources . W przypadku ustawień samochodu każdy indeksowany fragment jest opatrzony adnotacją @SearchIndexable , a następnie statycznym polem SearchIndexProvider , które dostarcza odpowiednich danych dla tego fragmentu. Fragmenty z adnotacją, ale brakuje w nich elementu 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 stanowi cienką warstwę otaczającą listę pól SearchIndexProvider dla wszystkich fragmentów. Dostępna jest obsługa określenia konkretnego celu adnotacji (takiego jak Auto, TV i Wear), jednak większość adnotacji pozostaje domyślna ( All ). W tym przypadku nie ma szczególnej potrzeby określania celu Auto, dlatego pozostaje on All . Podstawowa implementacja SearchIndexProvider ma być wystarczająca dla 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;
    }
}

Indeksuj nowy fragment

Dzięki temu projektowi stosunkowo łatwo jest dodać nowy SettingsFragment do zaindeksowania. Zwykle jest to dwuwierszowa aktualizacja zawierająca kod XML fragmentu i zamiar, którego należy przestrzegać. Na przykładzie 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 AAOS elementu SearchIndexablesProvider , która korzysta z SearchIndexableResources i wykonuje tłumaczenie z SearchIndexProviders na schemat bazy danych dla SettingsIntelligence , ale jest niezależna od indeksowanych fragmentów. SettingsIntelligence obsługuje dowolną liczbę dostawców, więc można tworzyć nowych dostawców w celu obsługi wyspecjalizowanych przypadków użycia, umożliwiając każdemu specjalizację i skupienie się na wynikach o podobnych strukturach. Każda z preferencji zdefiniowanych na PreferenceScreen dla fragmentu musi mieć przypisany unikalny klucz, aby można było je zaindeksować. Dodatkowo PreferenceScreen musi mieć przypisany klawisz, aby tytuł ekranu mógł zostać zaindeksowany.

Przykłady indeksów

W niektórych przypadkach z fragmentem może nie być skojarzona konkretna akcja intencji. W takich przypadkach możliwe jest przekazanie klasy aktywności do intencji CarBaseSearchIndexProvider przy użyciu komponentu, a nie akcji. Nadal wymaga to obecności działania w pliku manifestu i jego 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 zaistnieć potrzeba przesłonięcia niektórych metod CarBaseSearchIndexProvider , aby uzyskać żądane wyniki do indeksowania. Przykładowo we NetworkAndInternetFragment preferencje związane z siecią komórkową nie miały być indeksowane na urządzeniach bez sieci komórkowej. W takim przypadku należy zastąpić metodę getNonIndexableKeys i oznaczyć odpowiednie klucze jako nieindeksowalne , gdy urządzenie nie ma 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 w celu uwzględnienia innych danych indeksowalnych, takich jak surowe dane 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 wstrzykniętego indeksu

Aby wprowadzić ustawienie do indeksowania:

  1. Zdefiniuj SearchIndexablesProvider dla swojej aplikacji, rozszerzając klasę android.provider.SearchIndexablesProvider .
  2. Zaktualizuj plik AndroidManifest.xml aplikacji u dostawcy 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 indeksowane dane do swojego dostawcy. Implementacja zależy od potrzeb aplikacji. Można indeksować dwa różne typy danych: SearchIndexableResource i SearchIndexableRaw .

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