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ątrzCarSettings
. - 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 zdefiniowanoIntentTargetPackage
lubIntentTargetClass
. Określa akcję, jaką ma podjąć intencja wyniku wyszukiwania. -
IntentTargetPackage
. Wymagane , jeśliIntentAction
nie jest zdefiniowane. Definiuje pakiet, do którego ma zostać rozstrzygnięty cel wyniku wyszukiwania. -
IntentTargetClass
. Wymagane , jeśliIntentAction
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:
- Zdefiniuj
SearchIndexablesProvider
dla swojej aplikacji, rozszerzając klasęandroid.provider.SearchIndexablesProvider
. - 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>
- Dodaj indeksowane dane do swojego dostawcy. Implementacja zależy od potrzeb aplikacji. Można indeksować dwa różne typy danych:
SearchIndexableResource
iSearchIndexableRaw
.
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; } } }