Lập chỉ mục tìm kiếm trong phần Cài đặt ô tô

Tính năng tìm kiếm trong chế độ cài đặt giúp bạn nhanh chóng và dễ dàng tìm kiếm cũng như thay đổi phần cài đặt trong ứng dụng Automotive Settings (Cài đặt Automotive mà không cần di chuyển qua các trình đơn ứng dụng để tìm thấy nó. Tìm kiếm là cách hiệu quả nhất để tìm một chế độ cài đặt cụ thể. Theo mặc định, tính năng tìm kiếm chỉ tìm các chế độ cài đặt AOSP (Dự án nguồn mở Android). Các chế độ cài đặt bổ sung, cho dù có được chèn hay không, yêu cầu thay đổi bổ sung để được lập chỉ mục.

Yêu cầu

Để có thể lập chỉ mục một chế độ cài đặt bằng tính năng tìm kiếm trong phần Cài đặt, dữ liệu phải đến từ:

  • Mảnh SearchIndexable bên trong CarSettings.
  • Ứng dụng cấp hệ thống.

Xác định dữ liệu

Các trường phổ biến:

  • Key. (Bắt buộc) Khoá Chuỗi duy nhất mà con người có thể đọc được để xác định kết quả.
  • IconResId. Không bắt buộc Nếu một biểu tượng xuất hiện trong ứng dụng của bạn bên cạnh kết quả, sau đó thêm mã tài nguyên, nếu không hãy bỏ qua.
  • IntentAction. Bắt buộc nếu IntentTargetPackage hoặc IntentTargetClass chưa được xác định. Xác định hành động mà kết quả tìm kiếm ý định là muốn nhận.
  • IntentTargetPackage. Bắt buộc nếu IntentAction không phải là xác định. Xác định gói mà mục đích của kết quả tìm kiếm sẽ sử dụng.
  • IntentTargetClass. Bắt buộc nếu IntentAction không phải là xác định. Xác định lớp (hoạt động) mà ý định của kết quả tìm kiếm sẽ được xử lý.

Chỉ dành cho SearchIndexableResource:

  • XmlResId. (Bắt buộc) Xác định mã tài nguyên XML của trang chứa kết quả được lập chỉ mục.

Chỉ dành cho SearchIndexableRaw:

  • Title. (Bắt buộc) Tiêu đề của kết quả tìm kiếm.
  • SummaryOn. (Không bắt buộc) Bản tóm tắt của kết quả tìm kiếm.
  • Keywords. (Không bắt buộc) Danh sách các từ được liên kết với kết quả tìm kiếm. So khớp truy vấn với kết quả của bạn.
  • ScreenTitle. (Không bắt buộc) Tiêu đề của trang có kết quả tìm kiếm của bạn.

Ẩn dữ liệu

Mỗi kết quả tìm kiếm sẽ xuất hiện trong Tìm kiếm, trừ phi kết quả đó được đánh dấu khác. Khi ở trạng thái tĩnh kết quả tìm kiếm được lưu vào bộ nhớ đệm, một danh sách mới các khoá không lập chỉ mục được truy xuất mỗi lần nút tìm kiếm đã được mở. Lý do ẩn kết quả có thể bao gồm:

  • Sao chép. Ví dụ: xuất hiện trên nhiều trang.
  • Chỉ hiển thị có điều kiện. Ví dụ: chỉ hiển thị các chế độ cài đặt dữ liệu di động khi có thẻ SIM).
  • Trang theo mẫu. Ví dụ: trang chi tiết cho một ứng dụng riêng lẻ.
  • Chế độ cài đặt cần có nhiều bối cảnh hơn so với Tiêu đề và Phụ đề. Cho ví dụ: "Cài đặt" mà chỉ liên quan đến tiêu đề màn hình.

Để ẩn một chế độ cài đặt, nhà cung cấp của bạn hoặc SEARCH_INDEX_DATA_PROVIDER nên trả về khoá của kết quả tìm kiếm từ getNonIndexableKeys. Khoá có thể luôn được trả về (trường hợp trang trùng lặp, theo mẫu) hoặc được thêm theo điều kiện (không có thiết bị di động trường hợp dữ liệu).

Chỉ số tĩnh

Sử dụng chỉ mục tĩnh nếu dữ liệu chỉ mục của bạn luôn giữ nguyên. Ví dụ: tiêu đề và bản tóm tắt dữ liệu XML hoặc dữ liệu thô của mã cứng. Dữ liệu tĩnh được lập chỉ mục một lần khi tìm kiếm Cài đặt được khởi chạy lần đầu tiên.

Đối với các thành phần có thể lập chỉ mục bên trong phần cài đặt, hãy triển khai getXmlResourcesToIndex và/hoặc getRawDataToIndex. Đối với các chế độ cài đặt được chèn, hãy triển khai Phương thức queryXmlResources và/hoặc queryRawData.

Chỉ mục động

Nếu dữ liệu có thể lập chỉ mục có thể được cập nhật tương ứng, hãy sử dụng phương thức động để lập chỉ mục dữ liệu của bạn. Tính năng tìm kiếm trong mục Cài đặt sẽ cập nhật danh sách động này khi đã chạy.

Đối với các tài sản có thể lập chỉ mục bên trong phần cài đặt, hãy triển khai getDynamicRawDataToIndex. Đối với các chế độ cài đặt được chèn, hãy triển khai queryDynamicRawData methods.

Chỉ mục trong phần Cài đặt trên ô tô

Để lập chỉ mục các chế độ cài đặt khác nhau nhằm đưa vào tính năng tìm kiếm, hãy xem Nội dung . SettingsLibandroid.provider các gói đã có giao diện được định nghĩa và lớp trừu tượng để mở rộng cung cấp các mục mới cần được lập chỉ mục. Trong chế độ cài đặt AOSP, việc triển khai các cấu hình này được dùng để lập chỉ mục kết quả. Giao diện chính cần được thực hiện là SearchIndexablesProvider, được dùng bởi SettingsIntelligence để lập chỉ mục dữ liệu.

public abstract class SearchIndexablesProvider extends ContentProvider {
    public abstract Cursor queryXmlResources(String[] projection);
    public abstract Cursor queryRawData(String[] projection);
    public abstract Cursor queryNonIndexableKeys(String[] projection);
}

Theo lý thuyết, mỗi mảnh có thể được thêm vào kết quả theo SearchIndexablesProvider, trong trường hợp đó là SettingsIntelligence sẽ là nội dung. Để dễ dàng duy trì quy trình này bằng cách thêm các mảnh mới, sử dụng tính năng tạo mã SettingsLib của SearchIndexableResources. Riêng trong phần Cài đặt ô tô, mỗi mảnh có thể lập chỉ mục được chú thích bằng @SearchIndexable rồi có một SearchIndexProvider tĩnh cung cấp dữ liệu liên quan cho phân đoạn đó. Các mảnh có chú thích nhưng thiếu kết quả SearchIndexProvider dẫn đến quá trình biên dịch .

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

Sau đó, tất cả các mảnh này được thêm vào thư mục được tạo tự động Lớp SearchIndexableResourcesAuto là một trình bao bọc mỏng xung quanh danh sách các trường SearchIndexProvider cho tất cả các mảnh. Hỗ trợ được cung cấp để chỉ định mục tiêu cụ thể cho chú thích (chẳng hạn như Auto, TV và Wear), tuy nhiên, hầu hết các chú thích vẫn ở mặc định (All). Trong trường hợp sử dụng này, không có nhu cầu cụ thể nào để chỉ định Mục tiêu tự động, nên mục tiêu này vẫn được giữ nguyên với tên All. Phương thức triển khai cơ sở của SearchIndexProvider nhằm đủ cho hầu hết các mảnh.

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

Lập chỉ mục một mảnh mới

Với thiết kế này, bạn có thể dễ dàng thêm một SettingsFragment mới được lập chỉ mục, thường là nội dung cập nhật hai dòng cung cấp XML cho mảnh và ý định cần theo dõi. Với WifiSettingsFragment là ví dụ:

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

Việc triển khai AAOS của SearchIndexablesProvider, sử dụng SearchIndexableResources và thực hiện bản dịch từ SearchIndexProviders vào giản đồ cơ sở dữ liệu cho SettingsIntelligence, nhưng không xác định được mảnh là gì đang được lập chỉ mục. SettingsIntelligence hỗ trợ bất kỳ số lượng các nhà cung cấp khác để tạo ra các nhà cung cấp mới nhằm hỗ trợ việc sử dụng trường hợp, cho phép mỗi chỉ số được chuyên biệt và tập trung vào kết quả có cấu trúc. Các lựa chọn ưu tiên được xác định trong PreferenceScreen cho mỗi mảnh phải được gán một khoá duy nhất để được được lập chỉ mục. Ngoài ra, PreferenceScreen phải có khoá được gán cho tiêu đề màn hình sẽ được lập chỉ mục.

Ví dụ về chỉ mục

Trong một số trường hợp, một mảnh có thể không có thao tác theo ý định cụ thể nào được liên kết với nó. Trong những trường hợp như vậy, có thể chuyển lớp hoạt động vào Ý định CarBaseSearchIndexProvider sử dụng một thành phần thay vì một hành động. Thao tác này vẫn yêu cầu hoạt động phải có trong tệp kê khai và có thể xuất khẩu.

@SearchIndexable
public class LanguagesAndInputFragment extends SettingsFragment {
[...]
    public static final CarBaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
        new CarBaseSearchIndexProvider(R.xml.languages_and_input_fragment,
                LanguagesAndInputActivity.class);
}

Trong một số trường hợp đặc biệt, một số phương thức của CarBaseSearchIndexProvider có thể cần được ghi đè để kết quả mong muốn được lập chỉ mục. Ví dụ: trong NetworkAndInternetFragment, các lựa chọn ưu tiên liên quan đến mạng di động là không được lập chỉ mục trên các thiết bị không có mạng di động. Trong trường hợp này, hãy ghi đè getNonIndexableKeys rồi đánh dấu các khoá thích hợp là không lập chỉ mục được khi thiết bị không có mạng di động.

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

Tuỳ thuộc vào nhu cầu của mảnh cụ thể, các phương thức khác của trình CarBaseSearchIndexProvider có thể bị ghi đè để bao gồm các thuộc tính khác dữ liệu có thể lập chỉ mục, chẳng hạn như dữ liệu thô tĩnh và động.

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

Chế độ cài đặt được chèn chỉ mục

Để chèn một chế độ cài đặt cần được lập chỉ mục, hãy làm như sau:

  1. Xác định SearchIndexablesProvider cho ứng dụng của bạn bằng cách mở rộng Lớp android.provider.SearchIndexablesProvider.
  2. Cập nhật AndroidManifest.xml của ứng dụng bằng nhà cung cấp ở Bước 1. Định dạng như sau:
    <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. Thêm dữ liệu có thể lập chỉ mục vào nhà cung cấp của bạn. Việc triển khai phụ thuộc vào nhu cầu của ứng dụng. Có thể lập chỉ mục hai loại dữ liệu sau đây: SearchIndexableResourceSearchIndexableRaw.

Ví dụ về 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;
        }
    }
}