車輛設定搜尋索引

設定搜尋功能可讓你快速輕鬆地在 Automotive 設定應用程式中搜尋及變更特定設定,不必透過應用程式選單瀏覽即可找到所需設定。搜尋功能是找出特定設定最有效的方式。根據預設,搜尋功能只會找出 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》 套件已定義介面和抽象類別 提供要編入索引的新項目在 Android 開放原始碼計畫設定中, 類別可用來將結果編入索引。要完成的主要介面是 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 內容如要讓這項程序易於維護,並輕鬆新增新的片段,請使用 SearchIndexableResourcesSettingsLib 程式碼產生功能。專屬於車輛設定,每個可建立索引的片段都會以 @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 目標,因此會保留 AllSearchIndexProvider 的基本實作方式可滿足大多數片段的需求。

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 進行索引,通常會有兩行更新,為片段和 要追蹤的意圖以 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;
                }
            };
}

索引注入設定

如要插入要建立索引的設定,請按照下列步驟操作:

  1. 擴充 android.provider.SearchIndexablesProvider 類別,為應用程式定義 SearchIndexablesProvider
  2. 請使用步驟 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>
    
  3. 將可索引的資料新增至供應工具。實作方式取決於應用程式的需求。兩種不同的資料類型可進行索引:SearchIndexableResourceSearchIndexableRaw

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;
        }
    }
}