הוספה של הגדרות הרכב לאינדקס החיפוש

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

הגדרות של הזרקת אינדקס

כדי להוסיף הגדרה שרוצים להוסיף לאינדקס:

  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;
        }
    }
}