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ątrzCarSettings
. - 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ściIntentTargetPackage
aniIntentTargetClass
. Określa działanie, które ma wykonać intencja dotycząca wyniku wyszukiwania.IntentTargetPackage
. Wymagany, jeśli nie zdefiniowanoIntentAction
. Określa pakiet, który ma być rozwiązywany przez intencję wyniku wyszukiwania.IntentTargetClass
. Wymagany, jeśli nie zdefiniowanoIntentAction
. 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 SettingsLib
i android.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 SettingsIntelligence
bę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:
- Określ
SearchIndexablesProvider
dla swojej aplikacji, rozszerzając klasęandroid.provider.SearchIndexablesProvider
. - 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>
-
Dodaj do dostawcy dane, które można zindeksować. Implementacja zależy od potrzeb aplikacji. Można indeksować 2 typy danych:
SearchIndexableResource
iSearchIndexableRaw
.
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; } } }