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ątrzCarSettings
. - 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ściIntentTargetPackage
lubIntentTargetClass
. Definiuje działanie wykonywane w wyniku wyszukiwania podejmujemy działania.IntentTargetPackage
. Wymagane, jeśliIntentAction
nie jest zdefiniowane. Definiuje pakiet, z którym mają być kierowane wyniki wyszukiwania.IntentTargetClass
Wymagany, jeśliIntentAction
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 SettingsIntelligence
bę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:
- Określ
SearchIndexablesProvider
dla swojej aplikacji, rozszerzającandroid.provider.SearchIndexablesProvider
zajęcia. - 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>
-
Dodaj do swojego 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; } } }