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, dù có được chèn hay không, đều cần có thêm thay đổi để được lập chỉ mục.

Yêu cầu

Để một chế độ cài đặt có thể được lập chỉ mục 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ả, hãy thêm mã nhận dạng 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à ý định kết quả tìm kiếm sẽ thực hiệ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ã nhận dạng tài nguyên XML của trang chứa kết quả cần lập chỉ mục.

Chỉ SearchIndexableRaw:

  • Title. (Bắt buộc) Tiêu đề của kết quả tìm kiếm.
  • SummaryOn. (Không bắt buộc) Tóm tắt 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ở. Sau đây là một số lý do khiến kết quả bị ẩn:

  • 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 được tạo bằng 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ụ đề. Ví dụ: chế độ cài đặt "Cài đặt" chỉ liên quan đến tiêu đề màn hình.

Để ẩn một chế độ cài đặt, nhà cung cấp hoặc SEARCH_INDEX_DATA_PROVIDER phải 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 mục 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ìm kiếm trong phần Cài đặt sẽ cập nhật danh sách động này khi được khởi 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 ô 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 . Các gói SettingsLibandroid.provider đã xác định các giao diện và lớp trừu tượng để mở rộng nhằm cung cấp các mục nhập mới cần được lập chỉ mục. Trong phần cài đặt AOSP, các phương thức triển khai của các lớp 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 SearchIndexProvider sẽ dẫn đến lỗi 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 sẽ được thêm vào lớp SearchIndexableResourcesAuto được tạo tự động. Đây 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 dướ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ả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ợ số lượng nhà cung cấp bất kỳ, vì vậy, bạn có thể tạo nhà cung cấp mới để hỗ trợ các trường hợp sử dụng chuyên biệt, cho phép mỗi nhà cung cấp chuyên biệt và tập trung vào kết quả có cấu trúc tương tự. 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 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ó hành động theo ý định cụ thể liên kết với mảnh đó. Trong những trường hợp như vậy, bạn có thể truyền lớp hoạt động vào ý định CarBaseSearchIndexProvider bằng cách 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ể được xuất ra.

@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 sẽ 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 đè phương thức getNonIndexableKeys và đánh dấu các khoá thích hợp là không thể lập chỉ mụ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 chèn chỉ mục

Cách chèn chế độ cài đặt để được lập chỉ mục:

  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. Bạn có thể lập chỉ mục hai loại dữ liệu khác nhau: 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;
        }
    }
}