חיפוש ההגדרות מאפשר לכם לחפש הגדרות ספציפיות באפליקציית ההגדרות לכלי רכב ולשנות אותן במהירות ובקלות, בלי לנווט בתפריטי האפליקציה כדי למצוא אותן. החיפוש הוא הדרך היעילה ביותר למצוא הגדרה ספציפית. כברירת מחדל, החיפוש מוצא הגדרות של AOSP בלבד. הגדרות נוספות, גם אם הוטמעו וגם אם לא, מחייבות שינויים נוספים כדי להיכלל ב-Index.
דרישות
כדי שאפשר יהיה להוסיף הגדרה לאינדקס באמצעות חיפוש ההגדרות, הנתונים צריכים להגיע ממקור:
- קטע
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
. להגדרות מוזרקות, מטמיעים את ה-methods 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
מפתח כדי ששם המסך יתווסף לאינדקס.
דוגמאות לאינדקסים
במקרים מסוימים, יכול להיות שלא תהיה לקטע פעולת כוונת שימוש ספציפית שמשויכת אליו. במקרים כאלה, אפשר להעביר את סוג הפעילות ל-Intent 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; } } }