車輛設定搜尋索引

設定搜尋功能可讓你快速輕鬆地搜尋及變更 不必從應用程式選單前往 Automotive 設定頁面 並找到想要的程式。如要找出特定設定,最有效的方法就是使用搜尋功能。根據預設 搜尋只會找到 Android 開放原始碼計畫設定。其他設定 (無論插入與否), 需要進行其他變更才能編入索引

需求條件

如要讓設定搜尋可建立索引的設定,資料必須來自:

  • CarSettings 中的 SearchIndexable 片段。
  • 系統層級應用程式。

定義資料

常用欄位:

  • Key。(必要) 不重複的人類可讀字串鍵,用於識別結果。
  • IconResId選用:如果應用程式顯示的圖示位於 結果,然後加入資源 ID,否則請忽略。
  • IntentAction。如果 IntentTargetPackage 未定義 IntentTargetClass。定義搜尋結果的動作 最終意圖
  • IntentTargetPackage。如果不是 IntentAction,則為必填 定義搜尋結果意圖要解析的套件。
  • IntentTargetClass。如果不是 IntentAction,則為必填 定義搜尋結果意圖要解析的類別 (活動)。

僅限 SearchIndexableResource

  • XmlResId。(必要) 定義內含 將結果編入索引

僅限 SearchIndexableRaw

  • Title。(必要) 搜尋結果的標題。
  • SummaryOn。(選用) 搜尋結果摘要。
  • Keywords。(選用) 與搜尋結果相關聯的字詞清單。 比對查詢內容。
  • ScreenTitle。(選填) 搜尋結果所在的網頁標題。

隱藏資料

除非另有標示,否則每項搜尋結果都會顯示在 Google 搜尋中。靜態時 快取搜尋結果,且每次都會擷取無法建立索引的鍵清單 則已開啟。隱藏結果的原因可能包括:

  • 重複。舉例來說,廣告出現在多個網頁上。
  • 只在有條件的情況下顯示。例如,僅顯示行動數據設定 (如有 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 內容如要輕鬆維護新片段 使用 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)。 在本例中,自動指定不需要明確指定,因此會保留 使用 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. 擴充應用程式的 SearchIndexablesProvider android.provider.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. 將可建立索引的資料新增至供應器。導入方式將取決於 應用程式可建立索引的資料類型分為兩種: 《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;
        }
    }
}