نمایه سازی جستجوی تنظیمات خودرو

جستجوی تنظیمات به شما امکان می‌دهد تا به سرعت و به راحتی تنظیمات خاصی را در برنامه تنظیمات خودرو بدون پیمایش در منوهای برنامه برای یافتن آن جستجو کرده و تغییر دهید. جستجو موثرترین راه برای یافتن یک تنظیم خاص است. به طور پیش فرض، جستجو فقط تنظیمات AOSP را پیدا می کند. تنظیمات اضافی، چه تزریقی یا نه، برای ایندکس شدن نیاز به تغییرات اضافی دارند.

الزامات

برای اینکه یک تنظیم با جستجوی تنظیمات نمایه شود، داده‌ها باید از یک عبارت زیر باشند:

  • SearchIndexable بخش قابل نمایه سازی داخل CarSettings .
  • برنامه در سطح سیستم

داده ها را تعریف کنید

زمینه های مشترک:

  • Key . ( الزامی ) کلید رشته قابل خواندن منحصر به فرد برای شناسایی نتیجه.
  • IconResId . اختیاری اگر نمادی در برنامه شما در کنار نتیجه ظاهر شد، شناسه منبع را اضافه کنید، در غیر این صورت نادیده بگیرید.
  • IntentAction . اگر IntentTargetPackage یا IntentTargetClass تعریف نشده باشد، الزامی است . اقدامی را که نتیجه جستجو قرار است انجام دهد را مشخص می کند.
  • IntentTargetPackage . اگر IntentAction تعریف نشده باشد ، الزامی است . بسته ای را تعریف می کند که هدف نتیجه جستجو برای حل آن است.
  • IntentTargetClass . اگر IntentAction تعریف نشده باشد ، الزامی است . کلاس (فعالیتی) را که هدف نتیجه جستجو برای حل آن است، تعریف می کند.

فقط SearchIndexableResource :

  • XmlResId . ( الزامی ) شناسه منبع XML صفحه حاوی نتایجی که قرار است نمایه شوند را تعریف می کند.

فقط SearchIndexableRaw :

  • Title . ( الزامی ) عنوان نتیجه جستجو.
  • SummaryOn . ( اختیاری ) خلاصه ای از نتیجه جستجو.
  • Keywords . ( اختیاری ) فهرست کلمات مرتبط با نتیجه جستجو. پرس و جو را با نتیجه شما مطابقت می دهد.
  • ScreenTitle ( اختیاری ) عنوان صفحه با نتیجه جستجوی شما.

مخفی کردن داده ها

هر نتیجه جستجو در «جستجو» ظاهر می‌شود، مگر اینکه به شکل دیگری علامت‌گذاری شده باشد. در حالی که نتایج جستجوی ایستا در حافظه پنهان ذخیره می شوند، هر بار که جستجو باز می شود، فهرست جدیدی از کلیدهای غیرقابل نمایه سازی بازیابی می شود. دلایل پنهان کردن نتایج می تواند شامل موارد زیر باشد:

  • تکراری. به عنوان مثال، در چندین صفحه ظاهر می شود.
  • فقط مشروط نشان داده شده است. به عنوان مثال، تنظیمات داده تلفن همراه را فقط در صورت وجود سیم کارت نشان می دهد.
  • صفحه قالب. به عنوان مثال، یک صفحه جزئیات برای یک برنامه جداگانه.
  • تنظیم نیاز به زمینه بیشتری نسبت به عنوان و زیرنویس دارد. به عنوان مثال، یک تنظیم "تنظیمات" که فقط مربوط به عنوان صفحه است.

برای پنهان کردن یک تنظیم، ارائه‌دهنده شما یا 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 برای همه قطعات است. پشتیبانی برای تعیین یک هدف خاص برای یک حاشیه نویسی (مانند خودکار، تلویزیون، و پوشیدن) ارائه می شود، اما بیشتر حاشیه نویسی ها در حالت پیش فرض باقی می مانند ( 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. با گسترش کلاس android.provider.SearchIndexablesProvider برای برنامه خود یک 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;
        }
    }
}