يتيح لك البحث في الإعدادات البحث عن عناصر معينة وتغييرها بسرعة وسهولة الإعدادات في تطبيق "إعدادات السيارات" بدون التنقل عبر قوائم التطبيق إلى العثور عليها. إنّ البحث هو الطريقة الأكثر فعالية للعثور على خيار محدّد. بشكلٍ تلقائي، يعثر البحث على إعدادات 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
).
في حالة الاستخدام هذه، ما مِن حاجة محدّدة لتحديد الهدف Auto، لذا يبقى
على القيمة 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
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; } } }