通过设置搜索,您可以快速轻松地搜索和更改汽车设置应用中的特定设置,而无需浏览应用菜单来查找。搜索是查找特定设置的最有效方法。默认情况下,搜索仅查找 AOSP 设置。其他设置(无论是否注入)都需要进行其他更改才能编入索引。
要求
要使设置可由“设置”搜索编入索引,数据必须来自
CarSettings中的SearchIndexable片段。- 系统级应用。
定义数据
通用字段
Key。(必需)用于标识结果的唯一人类可读字符串键。IconResId。可选。如果图标显示在您的应用中结果旁边,则添加资源 ID,否则忽略。IntentAction。如果未定义IntentTargetPackage或IntentTargetClass,则为必需。定义搜索结果 intent 要执行的操作。IntentTargetPackage。如果未定义IntentAction,则为必需。定义搜索结果 intent 要解析到的软件包。IntentTargetClass。如果未定义IntentAction,则为必需。定义搜索结果 intent 要解析到的类(Activity)。
SearchIndexableResource 仅限
XmlResId。(必需)定义包含要编入索引的结果的页面的 XML 资源 ID。
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 is suppressed during search query. */ protected boolean isPageSearchEnabled(Context context) { return true; } }
索引新片段
通过此设计,可以相对轻松地添加要索引的新 SettingsFragment,通常是一个两行更新,提供片段的 XML 和要遵循的 intent。以 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); }
SearchIndexablesProvider 的 AAOS 实现使用 SearchIndexableResources 并执行从 SearchIndexProviders 到 SettingsIntelligence 的数据库架构的转换,但与正在索引的片段无关。SettingsIntelligence 支持任意数量的提供程序,因此可以创建新的提供程序来支持专门的用例,从而使每个提供程序都可以专门化并专注于具有相似结构的结果。为片段的 PreferenceScreen 中定义的偏好设置都必须分配一个唯一的键才能编入索引。此外,PreferenceScreen 必须分配一个键才能索引屏幕标题。
索引示例
在某些情况下,片段可能没有与之关联的特定 intent 操作。在这种情况下,可以使用组件而不是操作将 Activity 类传入 CarBaseSearchIndexProvider intent 中。这仍然需要 Activity 出现在清单文件中并导出。
@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; } }; }
索引注入的设置
要注入要索引的设置:
- 通过扩展
android.provider.SearchIndexablesProvider类,为您的应用定义SearchIndexablesProvider。 - 使用步骤 1 中的提供程序更新应用的
AndroidManifest.xml。格式为:<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; } } }