Поиск настроек позволяет быстро и легко находить и изменять определенные настройки в приложении Automotive Settings, не перемещаясь по меню приложения, чтобы найти их. Поиск — самый эффективный способ найти конкретную настройку. По умолчанию поиск находит только настройки 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
для всех фрагментов. Предусмотрена поддержка указания конкретной цели для аннотации (например, Auto, TV и Wear), однако для большинства аннотаций используется значение по умолчанию ( 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 will be 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; } } }