Поиск настроек позволяет быстро и легко искать и изменять определенные настройки в приложении «Настройки автомобиля» без необходимости перемещаться по меню приложения, чтобы найти их. Поиск — наиболее эффективный способ найти конкретную настройку. По умолчанию поиск находит только настройки AOSP. Дополнительные настройки, независимо от того, внедрены они или нет, требуют дополнительных изменений для индексации.
Требования
Чтобы параметр можно было индексировать с помощью поиска настроек, данные должны поступать из:
- Фрагмент
SearchIndexable
внутриCarSettings
. - Приложение системного уровня.
Определите данные
Общие поля:
-
Key
. ( Обязательно ) Уникальный удобочитаемый строковый ключ для идентификации результата. -
IconResId
. Необязательно. Если в вашем приложении рядом с результатом появляется значок, добавьте идентификатор ресурса, в противном случае игнорируйте. -
IntentAction
. Требуется , еслиIntentTargetPackage
илиIntentTargetClass
не определены. Определяет действие, которое должен выполнить результат поиска. -
IntentTargetPackage
. Требуется , еслиIntentAction
не определен. Определяет пакет, к которому должен быть разрешен результат поиска. -
IntentTargetClass
. Требуется , еслиIntentAction
не определен. Определяет класс (активность), к которому должен быть разрешен результат поиска.
Только SearchIndexableResource
:
-
XmlResId
. ( Обязательно ) Определяет идентификатор XML-ресурса страницы, содержащей результаты, подлежащие индексированию.
Только SearchIndexableRaw
:
-
Title
. ( Обязательно ) Название результата поиска. -
SummaryOn
. ( Необязательно ) Сводка результатов поиска. -
Keywords
. ( Необязательно ) Список слов, связанных с результатом поиска. Сопоставляет запрос с вашим результатом. -
ScreenTitle
. ( Необязательно ) Название страницы с результатами поиска.
Скрыть данные
Каждый результат поиска отображается в разделе «Поиск», если не указано иное. Хотя статические результаты поиска кэшируются, при каждом открытии поиска извлекается новый список неиндексируемых ключей. Причинами сокрытия результатов могут быть:
- Дубликат. Например, отображается на нескольких страницах.
- Показано только условно. Например, настройки мобильных данных отображаются только при наличии SIM-карты).
- Шаблонная страница. Например, страница сведений для отдельного приложения.
- Для настройки требуется больше контекста, чем заголовок и подзаголовок. Например, настройка «Настройки», которая относится только к заголовку экрана.
Чтобы скрыть параметр, ваш провайдер или SEARCH_INDEX_DATA_PROVIDER
должен вернуть ключ результата поиска из getNonIndexableKeys
. Ключ всегда можно вернуть (случаи дублирования, шаблонной страницы) или добавить по условию (без мобильных данных).
Статический индекс
Используйте статический индекс, если данные вашего индекса всегда одинаковы. Например, заголовок и сводка данных XML или необработанные данные жесткого кода. Статические данные индексируются только один раз при первом запуске поиска настроек.
Для индексируемых объектов внутри настроек реализуйте getXmlResourcesToIndex
и/или getRawDataToIndex
. Для внедренных параметров реализуйте методы queryXmlResources
и/или queryRawData
.
Динамический индекс
Если индексируемые данные можно соответствующим образом обновить, используйте динамический метод для индексации данных. Настройки поиска обновляют этот динамический список при его запуске.
Для индексируемых объектов внутри настроек реализуйте getDynamicRawDataToIndex
. Для внедренных настроек реализуйте queryDynamicRawData methods
.
Индекс в настройках автомобиля
Чтобы индексировать различные настройки, которые будут включены в функцию поиска, см. Поставщики контента . В пакетах SettingsLib
и android.provider
уже определены интерфейсы и абстрактные классы, которые можно расширить для предоставления новых записей для индексации. В настройках AOSP реализации этих классов используются для индексации результатов. Основным интерфейсом, который необходимо выполнить, является SearchIndexablesProvider
, который используется SettingsIntelligence
для индексации данных.
public abstract class SearchIndexablesProvider extends ContentProvider { public abstract Cursor queryXmlResources(String[] projection); public abstract Cursor queryRawData(String[] projection); public abstract Cursor queryNonIndexableKeys(String[] projection); }
Теоретически каждый фрагмент может быть добавлен к результатам в SearchIndexablesProvider
, и в этом случае SettingsIntelligence
будет содержимым. Чтобы упростить процесс поддержки и легко добавлять новые фрагменты, используйте генерацию кода SettingsLib
SearchIndexableResources
. В настройках автомобиля каждый индексируемый фрагмент помечается @SearchIndexable
, а затем имеет статическое поле SearchIndexProvider
, которое предоставляет соответствующие данные для этого фрагмента. Фрагменты с аннотацией, но без SearchIndexProvider
приводят к ошибке компиляции.
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); }
Все эти фрагменты затем добавляются в автоматически создаваемый класс SearchIndexableResourcesAuto
, который представляет собой тонкую оболочку списка полей SearchIndexProvider
для всех фрагментов. Предоставляется поддержка для указания конкретной цели для аннотации (например, «Авто», «ТВ» и «Износ»), однако для большинства аннотаций оставляются значения по умолчанию ( All
). В этом случае использования нет особой необходимости указывать цель Auto, поэтому она остается All
. Базовая реализация SearchIndexProvider
рассчитана на то, чтобы быть достаточной для большинства фрагментов.
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; } }
Индексировать новый фрагмент
При такой конструкции относительно легко добавить новый SettingsFragment
для индексации, обычно это двухстрочное обновление, предоставляющее XML для фрагмента и намерение, которому необходимо следовать. На примере 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); }
Реализация AAOS SearchIndexablesProvider
, которая использует SearchIndexableResources
и выполняет преобразование из SearchIndexProviders
в схему базы данных для SettingsIntelligence
, но не зависит от того, какие фрагменты индексируются. SettingsIntelligence
поддерживает любое количество поставщиков, поэтому можно создавать новых поставщиков для поддержки специализированных вариантов использования, позволяя каждому из них быть специализированным и ориентированным на результаты со схожими структурами. Каждому из предпочтений, определенных в PreferenceScreen
для фрагмента, должен быть присвоен уникальный ключ, чтобы его можно было индексировать. Кроме того, PreferenceScreen
должен иметь назначенный ключ для индексирования заголовка экрана.
Примеры индексов
В некоторых случаях с фрагментом может не быть связано конкретное намеренное действие. В таких случаях можно передать класс активности в намерение CarBaseSearchIndexProvider
, используя компонент, а не действие. Для этого по-прежнему требуется, чтобы действие присутствовало в файле манифеста и его можно было экспортировать.
@SearchIndexable public class LanguagesAndInputFragment extends SettingsFragment { [...] public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment, LanguagesAndInputActivity.class); }
В некоторых особых случаях некоторые методы CarBaseSearchIndexProvider
может потребоваться переопределить, чтобы получить желаемые результаты для индексации. Например, в NetworkAndInternetFragment
предпочтения, связанные с мобильной сетью, не должны были индексироваться на устройствах без мобильной сети. В этом случае переопределите метод getNonIndexableKeys
и пометьте соответствующие ключи как неиндексируемые, если на устройстве нет мобильной сети.
@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; } }; }
В зависимости от потребностей конкретного фрагмента другие методы CarBaseSearchIndexProvider
могут быть переопределены для включения других индексируемых данных, таких как статические и динамические необработанные данные.
@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; } }; }
Индексированные настройки
Чтобы ввести параметр для индексации:
- Определите
SearchIndexablesProvider
для своего приложения, расширив классandroid.provider.SearchIndexablesProvider
. - Обновите
AndroidManifest.xml
приложения, указав поставщика на шаге 1. Формат:<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>
- Добавьте индексируемые данные к своему провайдеру. Реализация зависит от потребностей приложения. Индексироваться могут два разных типа данных:
SearchIndexableResource
иSearchIndexableRaw
.
Пример 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; } } }