設定搜尋功能可讓你快速輕鬆地在 Automotive 設定應用程式中搜尋及變更特定設定,不必透過應用程式選單瀏覽即可找到所需設定。搜尋功能是找出特定設定最有效的方式。根據預設,搜尋功能只會找出 AOSP 設定。無論是否已插入,額外設定都需要進行額外變更才能建立索引。
需求條件
如要讓設定可供設定搜尋建立索引,資料必須來自下列來源:
SearchIndexable
片段位於CarSettings
內。- 系統層級應用程式。
定義資料
常見欄位:
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; } } }