汽車設置搜索索引

設置搜索使您能夠快速輕鬆地搜索和更改汽車設置應用程序中的特定設置,而無需瀏覽應用程序菜單來查找它。搜索是查找特定設置的最有效方式。默認情況下,搜索僅查找 AOSP 設置。額外的設置,無論是否注入,都需要額外的更改才能被索引。

概述

要求

對於設置搜索可索引的設置,數據必須來自:

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

定義數據

常用字段:

  • Key 。 (必需)用於識別結果的唯一人類可讀字符串鍵。
  • IconResId可選如果您的應用程序中結果旁邊出現圖標,則添加資源 id,否則忽略。
  • IntentAction 。如果IntentTargetPackageIntentTargetClass ,則為必需。定義搜索結果意圖要採取的操作。
  • 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

汽車設置中的索引

概述

要索引要包含在搜索功能中的不同設置,請參閱 內容提供程序SettingsLibandroid.provider包已經定義了接口和抽像類來擴展以提供要索引的新條目。在 AOSP 設置中,這些類的實現用於索引結果。要實現的主要接口是SearchIndexablesProviderSettingsIntelligence使用它來索引數據。

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 will be 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);
}

SearchIndexProviders SearchIndexablesProvider SearchIndexableResources 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;
        }
    }
}