فهرسة البحث في إعدادات السيارة

يتيح لك البحث في الإعدادات البحث عن عناصر معينة وتغييرها بسرعة وسهولة الإعدادات في تطبيق "إعدادات السيارات" بدون التنقل عبر قوائم التطبيق إلى العثور عليها. إنّ البحث هو الطريقة الأكثر فعالية للعثور على خيار محدّد. بشكل افتراضي، يعثر محرّك البحث على إعدادات AOSP فقط. بالإعدادات الإضافية، سواء تم إدخالها أو عدم إدخالها تتطلب تغييرات إضافية حتى تتم فهرستها.

المتطلبات

لكي يكون الإعداد قابلاً للفهرسة من خلال البحث في "الإعدادات"، يجب أن تأتي البيانات من:

  • جزء SearchIndexable داخل CarSettings.
  • تطبيق على مستوى النظام

تعريف البيانات

الحقول المشتركة:

  • Key (مطلوبة) مفتاح سلسلة فريد يمكن لشخص عادي قراءته لتحديد النتيجة.
  • IconResId اختياري إذا ظهر رمز في تطبيقك بجانب ثم أضف معرف المورد، وإلا فسيتم تجاهله.
  • IntentAction مطلوبة إذا كانت IntentTargetPackage أو لم يتم تحديد IntentTargetClass. يحدد الإجراء الذي تؤديه نتيجة البحث النية في أن يأخذ.
  • IntentTargetPackage مطلوبة إذا لم تكن القيمة "IntentAction" محددة. تحدد الحزمة التي تريد حل نتيجة البحث إليها.
  • IntentTargetClass مطلوبة إذا لم تكن القيمة "IntentAction" محددة. تُحدِّد الفئة (النشاط) التي تريد الانتقال إليها من خلال نتيجة البحث.

SearchIndexableResource فقط:

  • XmlResId (مطلوب) تحدد معرّف مورد XML للصفحة التي تحتوي على النتائج المراد فهرستها.

SearchIndexableRaw فقط:

  • Title (مطلوب) عنوان نتيجة البحث.
  • SummaryOn (اختياري) ملخّص نتيجة البحث
  • Keywords (اختياري) قائمة الكلمات المرتبطة بنتيجة البحث يطابق طلب البحث مع النتيجة.
  • ScreenTitle (اختياري) عنوان الصفحة التي تحتوي على نتيجة البحث

إخفاء البيانات

تظهر كل نتيجة بحث في "بحث Google" ما لم يتم وضع علامة عليها خلاف ذلك. بينما يكون ثابتًا يتم تخزين نتائج البحث مؤقتًا، ويتم استرداد قائمة جديدة بالمفاتيح غير القابلة للفهرسة في كل مرة تم فتح البحث. قد تتضمن أسباب إخفاء النتائج ما يلي:

  • نسخة طبق الأصل. على سبيل المثال، تظهر على عدة صفحات.
  • يتم عرضه بشروط فقط. على سبيل المثال، يعرض هذا الإعداد إعدادات بيانات الجوّال فقط. عند وجود شريحة SIM).
  • الصفحة المصمّمة وفقًا لنموذج: على سبيل المثال، صفحة تفاصيل لتطبيق فردي.
  • يحتاج الإعداد إلى سياق أكثر من العنوان والعنوان الفرعي. بالنسبة مثل "الإعدادات" والذي يرتبط فقط بعنوان الشاشة.

لإخفاء أحد الإعدادات، على مقدّم الخدمة أو SEARCH_INDEX_DATA_PROVIDER. إرجاع مفتاح نتيجة البحث من getNonIndexableKeys. يمكن للمفتاح يتم إرجاعها دائمًا (حالات صفحات مكررة أو مصمَّمة وفقًا لنموذج) أو إضافتها بشكل مشروط (بدون استخدام هاتف محمول حالة البيانات).

فهرس ثابت

استخدِم الفهرس الثابت إذا كانت بيانات الفهرس هي نفسها دائمًا. على سبيل المثال، العنوان وملخص بيانات XML أو البيانات الأولية ذات التعليمات البرمجية الثابتة. تمت فهرسة البيانات الثابتة مرة واحدة فقط عند تشغيل البحث في الإعدادات لأول مرة.

بالنسبة إلى العناصر القابلة للفهرسة داخل الإعدادات، نفِّذ 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 المحتوى. لتسهيل الحفاظ على عملية إضافة أجزاء جديدة، يمكنك استخدام عملية إنشاء الرموز SettingsLib من SearchIndexableResources. بالنسبة إلى "إعدادات السيارة"، تتم إضافة تعليق توضيحي لكل جزء قابل للفهرسة @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 لجميع الأجزاء. يتم تقديم الدعم لتحديد هدف معين للتعليق التوضيحي (مثل Auto وTV و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 جديدة. فهرسته، عادةً عبارة عن تحديث مؤلف من سطرين ويوفر ملف XML للجزء النية التي يجب اتباعها. إليك 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);
}

تتيح واجهة برمجة التطبيقات AAOS الخاصة بـ SearchIndexablesProvider، والتي تستخدم SearchIndexableResources و هل الترجمة من SearchIndexProviders في مخطط قاعدة البيانات SettingsIntelligence، لكنها غير مفهومة للأجزاء الفهرسة. تتيح 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. يمكنك تحديد SearchIndexablesProvider لتطبيقك من خلال تمديد صف واحد (android.provider.SearchIndexablesProvider).
  2. عدِّل AndroidManifest.xml للتطبيق من خلال موفِّر التطبيق في الخطوة 1. التنسيق هو:
    <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;
        }
    }
}