कार की सेटिंग के लिए खोज इंडेक्स करने की सुविधा

सेटिंग खोजने की सुविधा की मदद से, Automotive Settings ऐप्लिकेशन में किसी खास सेटिंग को आसानी से खोजा और बदला जा सकता है. इसके लिए, आपको ऐप्लिकेशन के मेन्यू में जाने की ज़रूरत नहीं है. किसी खास सेटिंग को ढूंढने का सबसे असरदार तरीका, खोजना है. डिफ़ॉल्ट रूप से, खोज में सिर्फ़ AOSP सेटिंग मिलती हैं. इंजेक्ट की गई या नहीं की गई, अतिरिक्त सेटिंग को इंडेक्स करने के लिए, उनमें कुछ और बदलाव करने की ज़रूरत होती है.

ज़रूरी शर्तें

सेटिंग खोज के हिसाब से किसी सेटिंग को इंडेक्स किया जा सकता है. इसके लिए, डेटा इनमें से किसी सोर्स से होना चाहिए:

  • CarSettings के अंदर SearchIndexable फ़्रैगमेंट.
  • सिस्टम-लेवल का ऐप्लिकेशन.

डेटा तय करना

सामान्य फ़ील्ड:

  • Key. (ज़रूरी है) नतीजे की पहचान करने के लिए, यूनीक और आसानी से पढ़ी जा सकने वाली स्ट्रिंग पासकोड.
  • IconResId. ज़रूरी नहीं अगर आपके ऐप्लिकेशन में नतीजे के बगल में कोई आइकॉन दिखता है, तो संसाधन आईडी जोड़ें. अगर नहीं दिखता है, तो इसे अनदेखा करें.
  • IntentAction. अगर IntentTargetPackage या IntentTargetClass की जानकारी नहीं दी गई है, तो ज़रूरी है. यह तय करता है कि खोज के नतीजे के मकसद के हिसाब से कौनसी कार्रवाई की जानी है.
  • IntentTargetPackage. अगर IntentAction की वैल्यू नहीं दी गई है, तो ज़रूरी है. उस पैकेज के बारे में बताता है जिसे खोज के नतीजे के इंटेंट से हल करना है.
  • IntentTargetClass. अगर IntentAction की वैल्यू नहीं दी गई है, तो ज़रूरी है. उस क्लास (गतिविधि) के बारे में बताता है जिसे खोज के नतीजे के इंटेंट से हल करना है.

सिर्फ़ SearchIndexableResource के लिए:

  • XmlResId. (ज़रूरी है) इंडेक्स किए जाने वाले नतीजों वाले पेज के एक्सएमएल रिसॉर्स आईडी की जानकारी देता है.

सिर्फ़ SearchIndexableRaw के लिए:

  • Title. (ज़रूरी है) खोज के नतीजे का टाइटल.
  • SummaryOn. (ज़रूरी नहीं) खोज के नतीजे की खास जानकारी.
  • Keywords. (ज़रूरी नहीं) खोज के नतीजे से जुड़े शब्दों की सूची. क्वेरी को आपके नतीजे से मैच करता है.
  • ScreenTitle. (ज़रूरी नहीं) खोज के नतीजे वाले पेज का टाइटल.

डेटा छिपाना

खोज के नतीजों में हर नतीजा तब तक दिखता है, जब तक उसे किसी दूसरे तरीके से मार्क नहीं किया जाता. स्टैटिक खोज के नतीजों को कैश मेमोरी में सेव किया जाता है. हालांकि, खोज खोलने पर, इंडेक्स नहीं की जा सकने वाली कुंजियों की एक नई सूची वापस लाई जाती है. नतीजों को छिपाने की ये वजहें हो सकती हैं:

  • डुप्लीकेट बनाएं. उदाहरण के लिए, एक से ज़्यादा पेजों पर दिखता है.
  • सिर्फ़ कुछ शर्तों के मुताबिक दिखाया जाता है. उदाहरण के लिए, सिम कार्ड मौजूद होने पर ही मोबाइल डेटा की सेटिंग दिखाता है).
  • टेंप्लेट वाला पेज. उदाहरण के लिए, किसी ऐप्लिकेशन की ज़्यादा जानकारी वाला पेज.
  • सेटिंग के लिए, टाइटल और सबटाइटल के अलावा ज़्यादा जानकारी की ज़रूरत होती है. उदाहरण के लिए, "सेटिंग" सेटिंग, जो सिर्फ़ स्क्रीन के टाइटल के लिए काम की है.

किसी सेटिंग को छिपाने के लिए, आपकी साइट को होस्ट करने वाली कंपनी या SEARCH_INDEX_DATA_PROVIDER को getNonIndexableKeys से खोज के नतीजे की कुंजी वापस करनी चाहिए. कुंजी को हमेशा दिखाया जा सकता है (डुप्लीकेट, टेंप्लेट वाले पेज के मामले) या शर्त के हिसाब से जोड़ा जा सकता है (मोबाइल डेटा का कोई मामला नहीं).

स्टैटिक इंडेक्स

अगर आपका इंडेक्स डेटा हमेशा एक जैसा रहता है, तो स्टैटिक इंडेक्स का इस्तेमाल करें. उदाहरण के लिए, एक्सएमएल डेटा का टाइटल और खास जानकारी या हार्ड कोड वाला रॉ डेटा. सेटिंग खोज की सुविधा पहली बार लॉन्च होने पर, स्टैटिक डेटा को सिर्फ़ एक बार इंडेक्स किया जाता है.

सेटिंग में इंडेक्स किए जा सकने वाले आइटम के लिए, getXmlResourcesToIndex और/या getRawDataToIndex लागू करें. इंजेक्ट की गई सेटिंग के लिए, queryXmlResources और/या queryRawData तरीके लागू करें.

डाइनैमिक इंडेक्स

अगर इंडेक्स किया जा सकने वाला डेटा उसी हिसाब से अपडेट किया जा सकता है, तो अपने डेटा को इंडेक्स करने के लिए, डाइनैमिक तरीके का इस्तेमाल करें. सेटिंग खोज की सुविधा चालू होने पर, यह डाइनैमिक सूची अपडेट हो जाती है.

सेटिंग में इंडेक्स किए जा सकने वाले आइटम के लिए, getDynamicRawDataToIndex लागू करें. इंजेक्ट की गई सेटिंग के लिए, queryDynamicRawData methods लागू करें.

कार की सेटिंग में इंडेक्स

खोज की सुविधा में शामिल करने के लिए, अलग-अलग सेटिंग को इंडेक्स करने के बारे में जानने के लिए, कॉन्टेंट उपलब्ध कराने वाली कंपनियां देखें. SettingsLib और android.provider पैकेज में पहले से ही तय इंटरफ़ेस और एब्स्ट्रैक्ट क्लास मौजूद हैं, ताकि इंडेक्स करने के लिए नई एंट्री दी जा सकें. AOSP सेटिंग में, नतीजों को इंडेक्स करने के लिए इन क्लास का इस्तेमाल किया जाता है. पूरा किया जाने वाला मुख्य इंटरफ़ेस SearchIndexablesProvider है. इसका इस्तेमाल SettingsIntelligence, डेटा को इंडेक्स करने के लिए करता है.

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

सिद्धांत रूप से, हर फ़्रैगमेंट को SearchIndexablesProvider में मौजूद नतीजों में जोड़ा जा सकता है. ऐसे में, SettingsIntelligence कॉन्टेंट होगा. नए फ़्रैगमेंट जोड़ने के लिए, इस प्रोसेस को आसानी से मैनेज करने के लिए, SearchIndexableResources के SettingsLib कोड जनरेशन का इस्तेमाल करें. कार की सेटिंग के लिए, इंडेक्स किए जा सकने वाले हर फ़्रैगमेंट को @SearchIndexable के साथ एनोटेट किया जाता है. इसके बाद, उसमें एक स्टैटिक SearchIndexProvider फ़ील्ड होता है, जो उस फ़्रैगमेंट के लिए काम का डेटा उपलब्ध कराता है. जिन फ़्रैगमेंट में एनोटेशन है, लेकिन SearchIndexProvider मौजूद नहीं है उनसे कंपाइल करने में गड़बड़ी होती है.

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

इसके बाद, इन सभी फ़्रैगमेंट को अपने-आप जनरेट होने वाली SearchIndexableResourcesAuto क्लास में जोड़ दिया जाता है. यह क्लास, सभी फ़्रैगमेंट के SearchIndexProvider फ़ील्ड की सूची के लिए एक थिन रैपर होती है. किसी एनोटेशन के लिए, किसी खास टारगेट (जैसे कि कार, टीवी, और Wear) को तय करने के लिए सहायता मिलती है. हालांकि, ज़्यादातर एनोटेशन को डिफ़ॉल्ट (All) पर छोड़ दिया जाता है. इस इस्तेमाल के उदाहरण में, कार टारगेट को तय करने की कोई खास ज़रूरत नहीं है, इसलिए इसे All के तौर पर ही रखा जाता है. SearchIndexProvider के बुनियादी तरीके को लागू करने से, ज़्यादातर फ़्रैगमेंट के लिए काफ़ी है.

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

किसी नए फ़्रैगमेंट को इंडेक्स करना

इस डिज़ाइन की मदद से, इंडेक्स करने के लिए नया SettingsFragment जोड़ना आसान है. आम तौर पर, यह दो लाइन का अपडेट होता है, जिसमें फ़्रैगमेंट के लिए एक्सएमएल और इस्तेमाल किए जाने वाले इंटेंट की जानकारी होती है. WifiSettingsFragment के उदाहरण के तौर पर:

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

SearchIndexablesProvider का AAOS लागू करने का तरीका, जो SearchIndexableResources का इस्तेमाल करता है और SettingsIntelligence के लिए, SearchIndexProviders से डेटाबेस स्कीमा में अनुवाद करता है. हालांकि, यह इस बात से अनजान है कि किन फ़्रैगमेंट को इंडेक्स किया जा रहा है. SettingsIntelligence में, सेवा देने वाली कई कंपनियों के साथ काम करने की सुविधा होती है. इसलिए, खास तरह के इस्तेमाल के उदाहरणों के लिए, नई कंपनियां बनाई जा सकती हैं. इससे हर कंपनी, मिलते-जुलते स्ट्रक्चर वाले नतीजों पर ध्यान दे पाती है. फ़्रेगमेंट के लिए PreferenceScreen में तय की गई प्राथमिकताओं को इंडेक्स करने के लिए, हर प्राथमिकता के लिए एक यूनीक कुंजी असाइन की जानी चाहिए. इसके अलावा, स्क्रीन के टाइटल को इंडेक्स करने के लिए, PreferenceScreen को एक कुंजी असाइन की जानी चाहिए.

इंडेक्स के उदाहरण

कुछ मामलों में, हो सकता है कि किसी फ़्रैगमेंट से कोई खास इंटेंट ऐक्शन न जुड़ा हो. ऐसे मामलों में, ऐक्शन के बजाय कॉम्पोनेंट का इस्तेमाल करके, CarBaseSearchIndexProvider इंटेंट में गतिविधि क्लास को पास किया जा सकता है. इसके लिए, अब भी ज़रूरी है कि गतिविधि, मेनिफ़ेस्ट फ़ाइल में मौजूद हो और उसे एक्सपोर्ट किया गया हो.

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

कुछ खास मामलों में, CarBaseSearchIndexProvider के कुछ तरीकों को बदलना पड़ सकता है, ताकि इंडेक्स किए जाने के लिए सही नतीजे मिल सकें. उदाहरण के लिए, NetworkAndInternetFragment में, मोबाइल नेटवर्क से जुड़ी प्राथमिकताओं को ऐसे डिवाइसों पर इंडेक्स नहीं किया जाना था जिनमें मोबाइल नेटवर्क नहीं है. इस मामले में, getNonIndexableKeys तरीके को बदलें और जब किसी डिवाइस पर मोबाइल नेटवर्क न हो, तो सही कुंजियों को इंडेक्स नहीं किया जा सकता के तौर पर मार्क करें.

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

किसी खास फ़्रैगमेंट की ज़रूरतों के हिसाब से, CarBaseSearchIndexProvider के अन्य तरीकों को बदला जा सकता है, ताकि इंडेक्स किया जा सकने वाला अन्य डेटा शामिल किया जा सके. जैसे, स्टैटिक और डाइनैमिक रॉ डेटा.

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

इंजेक्ट किए गए इंडेक्स की सेटिंग

इंडेक्स की जाने वाली सेटिंग को इंजेक्ट करने के लिए:

  1. android.provider.SearchIndexablesProvider क्लास को एक्सटेंड करके, अपने ऐप्लिकेशन के लिए SearchIndexablesProvider तय करें.
  2. पहले चरण में, सेवा देने वाली कंपनी के साथ ऐप्लिकेशन का AndroidManifest.xml अपडेट करें. इसका फ़ॉर्मैट यह है:
    <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. डेटा उपलब्ध करवाने वाली कंपनी को इंडेक्स किया जा सकने वाला डेटा जोड़ें. इसे लागू करने का तरीका, ऐप्लिकेशन की ज़रूरतों पर निर्भर करता है. दो अलग-अलग डेटा टाइप को इंडेक्स किया जा सकता है: SearchIndexableResource और SearchIndexableRaw.

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