Pengindeksan penelusuran Setelan Mobil

Penelusuran setelan memungkinkan Anda menelusuri dan mengubah setelan tertentu di aplikasi Setelan Otomotif dengan cepat dan mudah tanpa membuka menu aplikasi untuk menemukannya. Penelusuran adalah cara paling efektif untuk menemukan setelan tertentu. Secara default, penelusuran hanya menemukan setelan AOSP. Setelan tambahan, baik yang dimasukkan maupun tidak, memerlukan perubahan tambahan agar dapat diindeks.

Persyaratan

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

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

Menentukan data

Kolom umum:

  • Key. (Wajib) Kunci String unik yang dapat dibaca manusia untuk mengidentifikasi hasil.
  • IconResId. Opsional Jika ikon muncul di aplikasi Anda di samping hasil, lalu tambahkan ID resource. Jika tidak, abaikan.
  • IntentAction. Wajib jika IntentTargetPackage atau IntentTargetClass tidak ditentukan. Mendefinisikan tindakan yang akan diberikan hasil penelusuran maksudnya.
  • IntentTargetPackage. Wajib jika IntentAction tidak didefinisikan. Mendefinisikan paket yang akan diselesaikan oleh maksud hasil penelusuran.
  • IntentTargetClass. Wajib jika IntentAction tidak didefinisikan. Menentukan class (aktivitas) yang akan di-resolve oleh intent hasil penelusuran.

Khusus SearchIndexableResource:

  • XmlResId. (Wajib) Menentukan ID resource XML dari halaman yang berisi hasil yang akan diindeks.

Khusus SearchIndexableRaw:

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

Menyembunyikan data

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

  • Duplikat. Misalnya, muncul di beberapa halaman.
  • Hanya ditampilkan dengan syarat. Misalnya, hanya menampilkan setelan data seluler jika ada kartu SIM).
  • Halaman dengan template. Misalnya, halaman detail untuk setiap aplikasi.
  • Setelan memerlukan lebih banyak konteks daripada Judul dan Subtitel. Sebagai misalnya, "Setelan" yang hanya relevan dengan judul layar.

Untuk menyembunyikan setelan, penyedia atau SEARCH_INDEX_DATA_PROVIDER harus menampilkan kunci hasil penelusuran dari getNonIndexableKeys. Kuncinya dapat selalu ditampilkan (duplikat, kasus halaman bertemplate) atau ditambahkan bersyarat (tidak ada versi seluler {i>data case<i}).

Indeks statis

Gunakan indeks statis jika data indeks Anda selalu sama. Misalnya, judul dan ringkasan data XML atau data mentah hard code. Data statis diindeks hanya sekali saat penelusuran Setelan pertama kali diluncurkan.

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

Indeks dinamis

Jika data yang dapat diindeks dapat diperbarui sebagaimana mestinya, gunakan metode dinamis untuk mengindeks data Anda. Penelusuran setelan memperbarui daftar dinamis ini saat diluncurkan.

Untuk setelan yang dapat diindeks, terapkan getDynamicRawDataToIndex. Untuk setelan yang dimasukkan, implementasikan queryDynamicRawData methods.

Indeks di Setelan Mobil

Untuk mengindeks berbagai setelan agar disertakan dalam fitur penelusuran, lihat Konten penyedia layanan. SettingsLib dan android.provider paket sudah memiliki antarmuka yang ditentukan dan kelas abstrak untuk diperluas menyediakan entri baru untuk diindeks. Dalam setelan AOSP, implementasi digunakan untuk mengindeks hasil. Antarmuka utama yang akan 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, dengan demikian SettingsIntelligence akan menjadi konten. Agar proses dapat dikelola dengan mudah untuk menambahkan fragmen baru, gunakan pembuatan kode SettingsLib dari SearchIndexableResources. Khusus untuk Setelan Mobil, setiap fragmen yang dapat diindeks dianotasi dengan @SearchIndexable dan kemudian memiliki SearchIndexProvider statis yang menyediakan data yang relevan untuk fragmen tersebut. Fragmen dengan anotasi, tetapi tidak memiliki hasil SearchIndexProvider dalam kompilasi {i>error<i}.

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 class SearchIndexableResourcesAuto yang dibuat otomatis, yang merupakan wrapper tipis di sekitar daftar kolom SearchIndexProvider untuk semua fragmen. Dukungan disediakan untuk menentukan target tertentu untuk anotasi (seperti Auto, TV, dan Wear), tetapi sebagian besar anotasi dibiarkan pada setelan default (All). Dalam kasus penggunaan ini, tidak ada kebutuhan khusus untuk menentukan target Auto, sehingga tetap sebagai All. Implementasi dasar SearchIndexProvider dimaksudkan untuk mencukupi 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 is suppressed during search query.
     */
    protected boolean isPageSearchEnabled(Context context) {
        return true;
    }
}

Mengindeks fragmen baru

Dengan desain ini, relatif mudah untuk menambahkan SettingsFragment baru agar diindeks, biasanya update dua baris yang menyediakan XML untuk fragmen dan intent 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 bergantung pada fragmen yang diindeks. SettingsIntelligence mendukung sejumlah penyedia, sehingga penyedia baru dapat dibuat untuk mendukung kasus penggunaan khusus, yang memungkinkan setiap penyedia menjadi khusus dan berfokus pada hasil dengan struktur yang serupa. Setiap preferensi yang ditentukan dalam PreferenceScreen untuk fragmen harus memiliki kunci unik yang ditetapkan agar dapat diindeks. Selain itu, PreferenceScreen harus memiliki kunci yang ditetapkan agar judul layar dapat diindeks.

Contoh indeks

Dalam beberapa kasus, fragmen mungkin tidak memiliki tindakan intent tertentu yang terkait dengannya. Dalam kasus seperti itu, Anda bisa meneruskan class aktivitas ke Intent CarBaseSearchIndexProvider menggunakan komponen, bukan suatu tindakan. Ini masih memerlukan aktivitas 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 agar hasil yang diinginkan dapat diindeks. Misalnya, di NetworkAndInternetFragment, preferensi yang terkait dengan jaringan seluler tidak akan diindeks di perangkat tanpa jaringan seluler. Dalam hal ini, ganti getNonIndexableKeys dan tandai kunci yang sesuai sebagai tidak dapat diindeks jika 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;
                }
            };
}

Tergantung pada kebutuhan fragmen tertentu, metode lain dari CarBaseSearchIndexProvider mungkin diganti untuk menyertakan data 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;
                }
            };
}

Setelan indeks yang dimasukkan

Untuk memasukkan setelan yang akan diindeks:

  1. Tentukan SearchIndexablesProvider untuk aplikasi Anda dengan memperluas class android.provider.SearchIndexablesProvider.
  2. Update 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 bergantung pada kebutuhan aplikasi. Dua jenis 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;
        }
    }
}