設定検索を使用することで、Automotive 設定アプリの特定の設定をすばやく簡単に検索および変更でき、アプリのメニューを開いて見つける必要がなくなります。設定検索は、特定の設定を見つける最も効果的な方法です。デフォルトでは、AOSP の設定のみが検索されます。追加の設定をインデックス登録するには、それが挿入されているかどうかにかかわらず、追加の変更が必要になります。
概要
要件
設定検索で設定がインデックス登録されるようにするには、対象のデータが以下のデータである必要があります。
CarSettings
内のSearchIndexable
フラグメント。- システムレベルのアプリ。
データの定義
共通フィールド:
Key
。(必須)検索結果を判読するための、人が読める形式の一意の文字列キー。IconResId
。(省略可)アプリで検索結果の横にアイコンを表示する場合は、リソース ID を追加します。追加しない場合は、無視します。IntentAction
。IntentTargetPackage
またはIntentTargetClass
が定義されていない場合、必須。検索結果インテントで実行するアクションを定義します。IntentTargetPackage
。IntentAction
が定義されていない場合は必須。検索結果インテントが解決されるパッケージを定義します。IntentTargetClass
。IntentAction
が定義されていない場合は必須。検索結果インテントが解決されるクラス(アクティビティ)を定義します。
SearchIndexableResource
のみ:
XmlResId
。(必須)インデックス登録される検索結果を含むページの XML リソース ID を定義します。
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
はコンテンツです。新しいフラグメントを追加する処理を簡単に維持できるようにするには、SearchIndexableResources
の SettingsLib
コード生成を使用します。車の設定では、インデックス登録が可能な各フラグメントは @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 will be suppressed during search query. */ protected boolean isPageSearchEnabled(Context context) { return true; } }
新しいフラグメントのインデックス登録
この設計では、新しい SettingsFragment
を追加してインデックス登録することは比較的簡単です。通常は、フラグメントとその後のインテントの XML を提供する 2 行を更新します。たとえば、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); }
SearchIndexablesProvider
の AAOS 実装は 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; } }; }
挿入された設定のインデックス登録
概要
設定を挿入してインデックス登録するには:
android.provider.SearchIndexablesProvider
クラスを拡張して、アプリの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
の 2 種類のデータ型をインデックス登録できます。
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; } } }