يمكّنك البحث في الإعدادات من البحث بسرعة وسهولة عن إعدادات معينة وتغييرها في تطبيق إعدادات السيارات دون التنقل عبر قوائم التطبيقات للعثور عليها. البحث هو الطريقة الأكثر فعالية للعثور على إعداد معين. افتراضيًا، يبحث البحث عن إعدادات 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; } }; }
فهرس الإعدادات المحقونة
لإدخال الإعداد المراد فهرسته:
- حدد
SearchIndexablesProvider
لتطبيقك عن طريق توسيع فئةandroid.provider.SearchIndexablesProvider
. - قم بتحديث
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>
- أضف بيانات قابلة للفهرسة إلى المزود الخاص بك. يعتمد التنفيذ على احتياجات التطبيق. يمكن فهرسة نوعين مختلفين من البيانات:
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; } } }