Pengindeksan Pencarian Pengaturan Mobil

Pencarian pengaturan memungkinkan Anda dengan cepat dan mudah mencari dan mengubah pengaturan tertentu di aplikasi Pengaturan Otomotif tanpa menavigasi menu aplikasi untuk menemukannya. Pencarian adalah cara paling efektif untuk menemukan pengaturan tertentu. Secara default, pencarian hanya menemukan pengaturan AOSP. Pengaturan tambahan, apakah disuntikkan atau tidak, memerlukan perubahan tambahan agar dapat diindeks.

Ringkasan

Persyaratan

Agar setelan dapat diindeks oleh penelusuran Setelan, data harus berasal dari:

  • Fragmen SearchIndexable di dalam CarSettings .
  • Aplikasi tingkat sistem.

Mendefinisikan data

Bidang umum:

  • Key . ( Wajib ) Kunci String unik yang dapat dibaca manusia untuk mengidentifikasi hasilnya.
  • IconResId . Opsional Jika ikon muncul di aplikasi Anda di samping hasil, tambahkan id sumber daya, jika tidak abaikan.
  • IntentAction . Diperlukan jika IntentTargetPackage atau IntentTargetClass tidak ditentukan. Menentukan tindakan yang akan diambil oleh maksud hasil penelusuran.
  • IntentTargetPackage . Diperlukan jika IntentAction tidak ditentukan. Mendefinisikan paket yang menjadi tujuan hasil pencarian untuk diselesaikan.
  • IntentTargetClass . Diperlukan jika IntentAction tidak ditentukan. Mendefinisikan kelas (aktivitas) yang akan diselesaikan oleh maksud hasil pencarian.

Hanya SearchIndexableResource :

  • XmlResId . ( Wajib ) Mendefinisikan id sumber daya XML halaman yang berisi hasil yang akan diindeks.

Hanya SearchIndexableRaw :

  • Title . ( Wajib diisi ) Judul hasil pencarian.
  • SummaryOn . ( Opsional ) Ringkasan hasil pencarian.
  • Keywords . ( Opsional ) Daftar kata yang terkait dengan hasil pencarian. Mencocokkan kueri dengan hasil Anda.
  • ScreenTitle . ( Opsional ) Judul halaman dengan hasil pencarian Anda.

Menyembunyikan data

Setiap hasil pencarian akan muncul di Pencarian kecuali jika ditandai sebaliknya. Sementara hasil pencarian statis di-cache, daftar kunci baru yang tidak dapat diindeks diambil setiap kali pencarian dibuka. Alasan untuk menyembunyikan hasil dapat mencakup:

  • Duplikat. Misalnya, muncul di beberapa halaman.
  • Hanya ditampilkan bersyarat. Misalnya, hanya menampilkan pengaturan data seluler jika ada kartu SIM).
  • Halaman berpola. Misalnya, halaman detail untuk aplikasi individual.
  • Pengaturan membutuhkan lebih banyak konteks daripada Judul dan Subjudul. Misalnya, pengaturan "Pengaturan", yang hanya relevan dengan judul layar.

Untuk menyembunyikan setelan, penyedia Anda atau SEARCH_INDEX_DATA_PROVIDER harus mengembalikan kunci hasil penelusuran dari getNonIndexableKeys . Kunci selalu dapat dikembalikan (duplikat, kasus halaman bertemplat) atau ditambahkan secara kondisional (tidak ada kasus data seluler).

indeks statis

Gunakan indeks statis jika data indeks Anda selalu sama. Misalnya, judul dan ringkasan data XML atau data mentah kode keras. Data statis akan diindeks hanya sekali saat pencarian Pengaturan pertama kali diluncurkan.

Untuk yang dapat diindeks di dalam pengaturan, terapkan getXmlResourcesToIndex dan/atau getRawDataToIndex . Untuk pengaturan yang disuntikkan, terapkan metode queryXmlResources dan/atau queryRawData .

Indeks dinamis

Jika data yang dapat diindeks dapat diperbarui, gunakan metode dinamis untuk mengindeks data Anda. Pencarian pengaturan akan memperbarui daftar dinamis ini ketika diluncurkan.

Untuk yang dapat diindeks di dalam pengaturan, terapkan getDynamicRawDataToIndex . Untuk setelan yang disuntikkan, terapkan queryDynamicRawData methods .

Pengindeksan di Pengaturan Mobil

Ringkasan

Untuk mengindeks pengaturan yang berbeda untuk disertakan dalam fitur pencarian, lihat Penyedia konten . Paket SettingsLib dan android.provider sudah memiliki antarmuka yang ditentukan dan kelas abstrak yang diperluas untuk menyediakan entri baru yang akan diindeks. Dalam pengaturan AOSP, implementasi kelas-kelas ini digunakan untuk mengindeks hasil. Antarmuka utama yang harus dipenuhi adalah SearchIndexablesProvider , yang digunakan oleh SettingsIntelligence untuk mengindeks data.

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

Secara teori, setiap fragmen dapat ditambahkan ke hasil di SearchIndexablesProvider , dalam hal ini SettingsIntelligence akan menjadi konten. Agar proses mudah dipelihara dengan mudah untuk menambahkan fragmen baru, gunakan pembuatan kode SettingsLib dari SearchIndexableResources . Khusus untuk Pengaturan Mobil, setiap fragmen yang dapat diindeks dianotasi dengan @SearchIndexable dan kemudian memiliki bidang SearchIndexProvider statis yang menyediakan data yang relevan untuk fragmen tersebut. Fragmen dengan anotasi tetapi tidak memiliki SearchIndexProvider menghasilkan kesalahan kompilasi.

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

Semua fragmen ini kemudian ditambahkan ke kelas SearchIndexableResourcesAuto yang dibuat secara otomatis, yang merupakan pembungkus tipis di sekitar daftar bidang SearchIndexProvider untuk semua fragmen. Dukungan disediakan untuk menentukan target spesifik untuk anotasi (seperti Auto, TV, dan Wear) namun sebagian besar anotasi dibiarkan default ( All ). Dalam kasus penggunaan ini, tidak ada kebutuhan khusus untuk menentukan target Otomatis, sehingga tetap sebagai All . Implementasi dasar SearchIndexProvider dimaksudkan agar cukup untuk sebagian besar fragmen.

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

Mengindeks fragmen baru

Dengan desain ini, relatif mudah untuk menambahkan SettingsFragment baru untuk diindeks, biasanya pembaruan dua baris yang menyediakan XML untuk fragmen dan maksud yang harus diikuti. Dengan WifiSettingsFragment sebagai contoh:

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

Implementasi AAOS dari SearchIndexablesProvider , yang menggunakan SearchIndexableResources dan melakukan terjemahan dari SearchIndexProviders ke dalam skema database untuk SettingsIntelligence , tetapi tidak mengetahui fragmen apa yang sedang diindeks. SettingsIntelligence mendukung sejumlah penyedia, sehingga penyedia baru dapat dibuat untuk mendukung kasus penggunaan khusus, memungkinkan masing-masing untuk terspesialisasi dan fokus pada hasil dengan struktur serupa. Preferensi yang ditentukan dalam PreferenceScreen untuk fragmen masing-masing harus memiliki kunci unik yang ditetapkan untuk diindeks. Selain itu, PreferenceScreen harus memiliki kunci yang ditetapkan untuk judul layar yang akan diindeks.

Contoh pengindeksan

Dalam beberapa kasus, sebuah fragmen mungkin tidak memiliki tindakan maksud tertentu yang terkait dengannya. Dalam kasus seperti itu, dimungkinkan untuk meneruskan kelas aktivitas ke maksud CarBaseSearchIndexProvider menggunakan komponen, bukan tindakan. Ini masih memerlukan aktivitas untuk ada dalam file manifes dan untuk diekspor.

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

Dalam beberapa kasus khusus, beberapa metode CarBaseSearchIndexProvider mungkin perlu diganti untuk mendapatkan hasil yang diinginkan untuk diindeks. Misalnya, di NetworkAndInternetFragment , preferensi yang terkait dengan jaringan seluler tidak akan diindeks pada perangkat tanpa jaringan seluler. Dalam hal ini, ganti metode getNonIndexableKeys dan tandai kunci yang sesuai sebagai tidak dapat diindeks saat perangkat tidak memiliki jaringan seluler.

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

Bergantung pada kebutuhan fragmen tertentu, metode lain dari CarBaseSearchIndexProvider dapat diganti untuk menyertakan data lain yang dapat diindeks, seperti data mentah statis dan dinamis.

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

Mengindeks pengaturan yang disuntikkan

Ringkasan

Untuk menyuntikkan pengaturan yang akan diindeks:

  1. Tentukan SearchIndexablesProvider untuk aplikasi Anda dengan memperluas kelas android.provider.SearchIndexablesProvider .
  2. Perbarui AndroidManifest.xml aplikasi dengan penyedia di Langkah 1. Formatnya adalah:
    <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. Tambahkan data yang dapat diindeks ke penyedia Anda. Implementasinya tergantung pada kebutuhan aplikasi. Dua tipe data yang berbeda dapat diindeks: SearchIndexableResource dan SearchIndexableRaw .

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