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

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

متطلبات

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

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

تحديد البيانات

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

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

SearchIndexableResource فقط:

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

SearchIndexableRaw فقط:

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

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

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

  • ينسخ. على سبيل المثال، يظهر على صفحات متعددة.
  • يظهر فقط بشروط. على سبيل المثال، يعرض فقط إعدادات بيانات الهاتف المحمول في حالة وجود بطاقة 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 لجميع الأجزاء. يتم توفير الدعم لتحديد هدف محدد للتعليق التوضيحي (مثل تلقائي، وتلفزيون، و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;
        }
    }
}