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

Tìm kiếm cài đặt cho phép bạn tìm kiếm và thay đổi cài đặt cụ thể trong ứng dụng Cài đặt ô tô một cách nhanh chóng và dễ dàng mà không cần điều hướng qua menu ứng dụng để tìm. Tìm kiếm là cách hiệu quả nhất để tìm một cài đặt cụ thể. Theo mặc định, tìm kiếm chỉ tìm thấy cài đặt AOSP. Các cài đặt bổ sung, dù được thêm vào hay không, đều yêu cầu thay đổi bổ sung để được lập chỉ mục.

Yêu cầu

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

  • Đoạn 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 ) Khóa Chuỗi duy nhất mà con người có thể đọc được để xác định kết quả.
  • IconResId . Tùy chọn 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ả thì hãy thêm id tài nguyên, nếu không thì bỏ qua.
  • IntentAction . Bắt buộc nếu IntentTargetPackage hoặc IntentTargetClass không được xác định. Xác định hành động mà mục đích của kết quả tìm kiếm sẽ thực hiện.
  • IntentTargetPackage . Bắt buộc nếu IntentAction không được xác định. Xác định gói mà mục đích của kết quả tìm kiếm là giải quyết.
  • IntentTargetClass . Bắt buộc nếu IntentAction không được xác định. Xác định lớp (hoạt động) mà mục đích của kết quả tìm kiếm sẽ giải quyết.

Chỉ SearchIndexableResource :

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

Chỉ SearchIndexableRaw chỉ mụcRaw:

  • Title . ( Bắt buộc ) Tiêu đề của kết quả tìm kiếm.
  • SummaryOn . ( Tùy chọn ) Tóm tắt kết quả tìm kiếm.
  • Keywords . ( Tùy chọn ) Danh sách các từ liên quan đến kết quả tìm kiếm. Khớp truy vấn với kết quả của bạn.
  • ScreenTitle . ( Tùy chọn ) 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ừ khi được đánh dấu khác. Trong khi các kết quả tìm kiếm tĩnh được lưu vào bộ nhớ đệm, một danh sách mới các khóa không thể lập chỉ mục sẽ được truy xuất mỗi khi mở tìm kiếm. Lý do ẩn kết quả có thể bao gồm:

  • Nhân bản. 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à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ẻ.
  • Cài đặt cần nhiều ngữ cảnh hơn Tiêu đề và Phụ đề. Ví dụ: cài đặt "Cài đặt", chỉ liên quan đến tiêu đề màn hình.

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

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ống nhau. Ví dụ: tiêu đề và tóm tắt dữ liệu XML hoặc dữ liệu thô mã cứng. Dữ liệu tĩnh chỉ đượ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.

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

Chỉ số độ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 pháp động để lập chỉ mục dữ liệu của bạn. Tìm kiếm cài đặt cập nhật danh sách động này khi nó được khởi chạy.

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

Chỉ mục trong Cài đặt ô tô

Để lập chỉ mục các cài đặt khác nhau sẽ được đưa vào tính năng tìm kiếm, hãy xem Nhà cung cấp nội dung . Các gói SettingsLibandroid.provider đã có các giao diện và lớp trừu tượng được xác định để mở rộng nhằm cung cấp các mục mới được lập chỉ mục. Trong cài đặt AOSP, việc triển khai các lớp này được sử dụng để lập chỉ mục kết quả. Giao diện chính cần thực hiện là SearchIndexablesProvider , giao diện này được SettingsIntelligence sử dụng để 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);
}

Về lý thuyết, mỗi đoạn có thể được thêm vào kết quả trong SearchIndexablesProvider , trong trường hợp đó, SettingsIntelligence sẽ là nội dung. Để làm cho quá trình dễ dàng duy trì và dễ dàng thêm các đoạn mới, hãy sử dụng quá trình tạo mã SettingsLib của SearchIndexableResources . Cụ thể đối với Cài đặt ô tô, mỗi đoạn có thể lập chỉ mục được chú thích bằng @SearchIndexable , sau đó có trường SearchIndexProvider tĩnh cung cấp dữ liệu liên quan cho đoạn đó. Các đoạn 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 phân đoạn này sẽ được thêm vào lớp SearchIndexableResourcesAuto được tạo tự động, đây là lớp bao bọc mỏng bao quanh danh sách các trường SearchIndexProvider cho tất cả các phân đoạn. Hỗ trợ được cung cấp để chỉ định mục tiêu cụ thể cho chú thích (chẳng hạn như Ô tô, TV và Wear) tuy nhiên hầu hết các chú thích đều được để ở 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, vì vậy nó vẫn giữ nguyên là All . Việc triển khai cơ bản của SearchIndexProvider được dự định là đủ cho hầu hết các phân đoạn.

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 đoạn mới

Với thiết kế này, việc thêm một SettingsFragment mới để lập chỉ mục là tương đối dễ dàng, thường là một bản cập nhật hai dòng cung cấp XML cho đoạn đó và mục đích được tuân theo. Lấy WifiSettingsFragment làm 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 chuyển đổi từ SearchIndexProviders sang lược đồ cơ sở dữ liệu cho SettingsIntelligence , nhưng không biết phân đoạn nào đang được lập chỉ mục. SettingsIntelligence hỗ trợ bất kỳ số lượng nhà cung cấp nào, do đó, các nhà cung cấp mới có thể được tạo để hỗ trợ các trường hợp sử dụng chuyên biệt, cho phép mỗi nhà cung cấp trở nên chuyên biệt và tập trung vào kết quả có cấu trúc tương tự. Mỗi tùy chọn được xác định trong PreferenceScreen cho đoạn phải có một khóa duy nhất được gán cho chúng để được lập chỉ mục. Ngoài ra, PreferenceScreen phải được gán một khóa để lập chỉ mục tiêu đề màn hình.

Ví dụ chỉ mục

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

@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 pháp của CarBaseSearchIndexProvider có thể cần được ghi đè để lập chỉ mục kết quả mong muốn. Ví dụ: trong NetworkAndInternetFragment , các tùy chọ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 khóa 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;
                }
            };
}

Tùy thuộc vào nhu cầu của phân đoạn cụ thể, các phương thức khác của CarBaseSearchIndexProvider có thể bị ghi đè để bao gồm dữ liệu có thể lập chỉ mục khá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;
                }
            };
}

Cài đặt được chèn chỉ mục

Để thêm một cài đặt cần đượ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 với nhà cung cấp ở Bước 1. Định dạng là:
    <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. Hai loại dữ liệu khác nhau có thể được lập chỉ mục: 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;
        }
    }
}