設定搜尋可讓您快速輕鬆地搜尋並更改「汽車設定」應用程式中的特定設置,而無需瀏覽應用程式選單來尋找它。搜尋是尋找特定設定的最有效方法。預設情況下,搜尋僅查找 AOSP 設定。其他設置,無論是否注入,都需要進行其他更改才能編制索引。
要求
對於可透過設定搜尋進行索引的設置,資料必須來自:
-
CarSettings
內的SearchIndexable
片段。 - 系統級應用程式。
定義數據
常見欄位:
-
Key
。 (必需)用於識別結果的唯一人類可讀字串鍵。 -
IconResId
。可選如果應用程式中結果旁邊出現圖標,則添加資源 ID,否則忽略。 -
IntentAction
。如果未定義IntentTargetPackage
或IntentTargetClass
,則為必要。定義搜尋結果意圖要採取的操作。 -
IntentTargetPackage
。如果未定義IntentAction
,則為必要。定義搜尋結果意圖解析到的套件。 -
IntentTargetClass
。如果未定義IntentAction
,則為必要。定義搜尋結果意圖要解析到的類別(活動)。
僅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
就是內容。為了讓流程易於維護並輕鬆新增片段,請使用SearchIndexableResources
的SettingsLib
程式碼產生。特定於汽車設置,每個可索引片段都以@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 和要遵循的意圖。以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
必須指派一個鍵才能對螢幕標題進行索引。
索引範例
在某些情況下,片段可能沒有與其關聯的特定意圖操作。在這種情況下,可以使用元件而不是操作將活動類別傳遞到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; } }; }
索引註入設定
注入要索引的設定:
- 透過擴充
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; } } }