Android TV は、Android 検索インターフェースを使用して、インストール済みのアプリからコンテンツ データを取得し、検索結果をユーザーに配信します。これらの結果にアプリのコンテンツ データを含めると、ユーザーはアプリ内のコンテンツにすぐにアクセスできます。
アプリでは、ユーザーが検索ダイアログに文字を入力したときにおすすめの検索結果を生成できるデータ フィールドのデータを Android TV に提供する必要があります。そのためには、候補を提供するコンテンツ プロバイダを、Android TV 用のコンテンツ プロバイダとその他の重要な情報を記述した
searchable.xml
構成ファイルとともにアプリに実装する必要があります。また、ユーザーが検索結果の候補を選択したときに起動されるインテントを処理するアクティビティも必要です。詳細については、カスタム検索候補を追加するをご覧ください。このガイドでは、Android TV アプリに固有の要点について説明します。
このガイドを読む前に、Search API ガイドで説明されているコンセプトを理解しておいてください。また、検索機能の追加もご覧ください。
このガイドのサンプルコードは、 Leanback サンプルアプリのものです。
列を特定する
SearchManager
は、想定されるデータ フィールドをローカル データベースの列として表すことで記述します。データの形式に関係なく、通常はコンテンツ データにアクセスするクラス内で、データ フィールドをこれらの列にマッピングする必要があります。既存のデータを必須フィールドにマッピングするクラスの作成方法については、
候補テーブルの作成をご覧ください。
SearchManager
クラスには、Android TV 用の列が複数含まれています。次の表に、重要な列の一部を示します。
値 | 説明 |
---|---|
SUGGEST_COLUMN_TEXT_1 |
コンテンツの名前(必須) |
SUGGEST_COLUMN_TEXT_2 |
コンテンツの説明テキスト |
SUGGEST_COLUMN_RESULT_CARD_IMAGE |
コンテンツ用の画像、ポスター、表紙 |
SUGGEST_COLUMN_CONTENT_TYPE |
メディアの MIME タイプ |
SUGGEST_COLUMN_VIDEO_WIDTH |
メディアの解像度の幅 |
SUGGEST_COLUMN_VIDEO_HEIGHT |
メディアの解像度の高さ |
SUGGEST_COLUMN_PRODUCTION_YEAR |
コンテンツの制作年(必須) |
SUGGEST_COLUMN_DURATION |
メディアの再生時間(ミリ秒単位)(必須) |
検索フレームワークには次の列が必要です。
コンテンツのこれらの列の値が、Google サーバーで検出された他のプロバイダからの同じコンテンツの値と一致する場合、コンテンツの詳細ビューには、他のプロバイダのアプリへのリンクとともに、アプリへのディープリンクが表示されます。詳しくは、詳細画面のアプリへのディープリンクのセクションをご覧ください。
アプリケーションのデータベース クラスでは、次のように列を定義します。
Kotlin
class VideoDatabase { companion object { // The columns we'll include in the video database table val KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1 val KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2 val KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE val KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE val KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE val KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH val KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT val KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG val KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE val KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE val KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE val KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE val KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR val KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION val KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION ... } ... }
Java
public class VideoDatabase { // The columns we'll include in the video database table public static final String KEY_NAME = SearchManager.SUGGEST_COLUMN_TEXT_1; public static final String KEY_DESCRIPTION = SearchManager.SUGGEST_COLUMN_TEXT_2; public static final String KEY_ICON = SearchManager.SUGGEST_COLUMN_RESULT_CARD_IMAGE; public static final String KEY_DATA_TYPE = SearchManager.SUGGEST_COLUMN_CONTENT_TYPE; public static final String KEY_IS_LIVE = SearchManager.SUGGEST_COLUMN_IS_LIVE; public static final String KEY_VIDEO_WIDTH = SearchManager.SUGGEST_COLUMN_VIDEO_WIDTH; public static final String KEY_VIDEO_HEIGHT = SearchManager.SUGGEST_COLUMN_VIDEO_HEIGHT; public static final String KEY_AUDIO_CHANNEL_CONFIG = SearchManager.SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG; public static final String KEY_PURCHASE_PRICE = SearchManager.SUGGEST_COLUMN_PURCHASE_PRICE; public static final String KEY_RENTAL_PRICE = SearchManager.SUGGEST_COLUMN_RENTAL_PRICE; public static final String KEY_RATING_STYLE = SearchManager.SUGGEST_COLUMN_RATING_STYLE; public static final String KEY_RATING_SCORE = SearchManager.SUGGEST_COLUMN_RATING_SCORE; public static final String KEY_PRODUCTION_YEAR = SearchManager.SUGGEST_COLUMN_PRODUCTION_YEAR; public static final String KEY_COLUMN_DURATION = SearchManager.SUGGEST_COLUMN_DURATION; public static final String KEY_ACTION = SearchManager.SUGGEST_COLUMN_INTENT_ACTION; ...
SearchManager
列からデータ フィールドへのマップを作成するときは、_ID
も指定して各行に一意の ID を割り当てる必要があります。
Kotlin
companion object { .... private fun buildColumnMap(): Map<String, String> { return mapOf( KEY_NAME to KEY_NAME, KEY_DESCRIPTION to KEY_DESCRIPTION, KEY_ICON to KEY_ICON, KEY_DATA_TYPE to KEY_DATA_TYPE, KEY_IS_LIVE to KEY_IS_LIVE, KEY_VIDEO_WIDTH to KEY_VIDEO_WIDTH, KEY_VIDEO_HEIGHT to KEY_VIDEO_HEIGHT, KEY_AUDIO_CHANNEL_CONFIG to KEY_AUDIO_CHANNEL_CONFIG, KEY_PURCHASE_PRICE to KEY_PURCHASE_PRICE, KEY_RENTAL_PRICE to KEY_RENTAL_PRICE, KEY_RATING_STYLE to KEY_RATING_STYLE, KEY_RATING_SCORE to KEY_RATING_SCORE, KEY_PRODUCTION_YEAR to KEY_PRODUCTION_YEAR, KEY_COLUMN_DURATION to KEY_COLUMN_DURATION, KEY_ACTION to KEY_ACTION, BaseColumns._ID to ("rowid AS " + BaseColumns._ID), SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID), SearchManager.SUGGEST_COLUMN_SHORTCUT_ID to ("rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID) ) } }
Java
... private static HashMap<String, String> buildColumnMap() { HashMap<String, String> map = new HashMap<String, String>(); map.put(KEY_NAME, KEY_NAME); map.put(KEY_DESCRIPTION, KEY_DESCRIPTION); map.put(KEY_ICON, KEY_ICON); map.put(KEY_DATA_TYPE, KEY_DATA_TYPE); map.put(KEY_IS_LIVE, KEY_IS_LIVE); map.put(KEY_VIDEO_WIDTH, KEY_VIDEO_WIDTH); map.put(KEY_VIDEO_HEIGHT, KEY_VIDEO_HEIGHT); map.put(KEY_AUDIO_CHANNEL_CONFIG, KEY_AUDIO_CHANNEL_CONFIG); map.put(KEY_PURCHASE_PRICE, KEY_PURCHASE_PRICE); map.put(KEY_RENTAL_PRICE, KEY_RENTAL_PRICE); map.put(KEY_RATING_STYLE, KEY_RATING_STYLE); map.put(KEY_RATING_SCORE, KEY_RATING_SCORE); map.put(KEY_PRODUCTION_YEAR, KEY_PRODUCTION_YEAR); map.put(KEY_COLUMN_DURATION, KEY_COLUMN_DURATION); map.put(KEY_ACTION, KEY_ACTION); map.put(BaseColumns._ID, "rowid AS " + BaseColumns._ID); map.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); map.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID, "rowid AS " + SearchManager.SUGGEST_COLUMN_SHORTCUT_ID); return map; } ...
前の例で、SUGGEST_COLUMN_INTENT_DATA_ID
フィールドへのマッピングに注目してください。これは、この行のデータに固有のコンテンツを指す URI の部分です。URI の最後の部分は、コンテンツの格納場所を表します。検索候補を処理するセクションで説明しているように、URI の最初の部分(テーブル内のすべての行に共通する場合)は、 searchable.xml
ファイルで
android:searchSuggestIntentData
属性として設定されます。
URI の最初の部分がテーブルの各行で異なる場合は、その値を SUGGEST_COLUMN_INTENT_DATA
フィールドにマッピングします。ユーザーがこのコンテンツを選択すると、起動されるインテントは、SUGGEST_COLUMN_INTENT_DATA_ID
と、android:searchSuggestIntentData
属性または SUGGEST_COLUMN_INTENT_DATA
フィールド値の組み合わせから、インテント データを提供します。
検索候補データを提供する
Android TV の検索ダイアログに検索キーワードの候補を返すコンテンツ プロバイダを実装します。文字が入力されるたびに query()
メソッドを呼び出して、コンテンツ プロバイダに候補を照会します。query()
の実装で、コンテンツ プロバイダは候補データを検索し、候補用に指定した行を指す Cursor
を返します。
Kotlin
fun query(uri: Uri, projection: Array<String>, selection: String, selectionArgs: Array<String>, sortOrder: String): Cursor { // Use the UriMatcher to see what kind of query we have and format the db query accordingly when (URI_MATCHER.match(uri)) { SEARCH_SUGGEST -> { Log.d(TAG, "search suggest: ${selectionArgs[0]} URI: $uri") if (selectionArgs == null) { throw IllegalArgumentException( "selectionArgs must be provided for the Uri: $uri") } return getSuggestions(selectionArgs[0]) } else -> throw IllegalArgumentException("Unknown Uri: $uri") } } private fun getSuggestions(query: String): Cursor { val columns = arrayOf<String>( BaseColumns._ID, VideoDatabase.KEY_NAME, VideoDatabase.KEY_DESCRIPTION, VideoDatabase.KEY_ICON, VideoDatabase.KEY_DATA_TYPE, VideoDatabase.KEY_IS_LIVE, VideoDatabase.KEY_VIDEO_WIDTH, VideoDatabase.KEY_VIDEO_HEIGHT, VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, VideoDatabase.KEY_PURCHASE_PRICE, VideoDatabase.KEY_RENTAL_PRICE, VideoDatabase.KEY_RATING_STYLE, VideoDatabase.KEY_RATING_SCORE, VideoDatabase.KEY_PRODUCTION_YEAR, VideoDatabase.KEY_COLUMN_DURATION, VideoDatabase.KEY_ACTION, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID ) return videoDatabase.getWordMatch(query.toLowerCase(), columns) }
Java
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Use the UriMatcher to see what kind of query we have and format the db query accordingly switch (URI_MATCHER.match(uri)) { case SEARCH_SUGGEST: Log.d(TAG, "search suggest: " + selectionArgs[0] + " URI: " + uri); if (selectionArgs == null) { throw new IllegalArgumentException( "selectionArgs must be provided for the Uri: " + uri); } return getSuggestions(selectionArgs[0]); default: throw new IllegalArgumentException("Unknown Uri: " + uri); } } private Cursor getSuggestions(String query) { query = query.toLowerCase(); String[] columns = new String[]{ BaseColumns._ID, VideoDatabase.KEY_NAME, VideoDatabase.KEY_DESCRIPTION, VideoDatabase.KEY_ICON, VideoDatabase.KEY_DATA_TYPE, VideoDatabase.KEY_IS_LIVE, VideoDatabase.KEY_VIDEO_WIDTH, VideoDatabase.KEY_VIDEO_HEIGHT, VideoDatabase.KEY_AUDIO_CHANNEL_CONFIG, VideoDatabase.KEY_PURCHASE_PRICE, VideoDatabase.KEY_RENTAL_PRICE, VideoDatabase.KEY_RATING_STYLE, VideoDatabase.KEY_RATING_SCORE, VideoDatabase.KEY_PRODUCTION_YEAR, VideoDatabase.KEY_COLUMN_DURATION, VideoDatabase.KEY_ACTION, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID }; return videoDatabase.getWordMatch(query, columns); } ...
マニフェスト ファイルで、コンテンツ プロバイダは特別な扱いを受けます。アクティビティとしてタグ付けされるのではなく、<provider>
として記述します。プロバイダには、コンテンツ プロバイダの名前空間をシステムに知らせる android:authorities
属性が含まれています。また、Android のグローバル検索で返された結果を使用できるように、android:exported
属性を "true"
に設定する必要があります。
<provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" />
検索候補を処理する
検索候補の設定を構成するには、アプリに
res/xml/searchable.xml
ファイルを含める必要があります。
res/xml/searchable.xml
ファイルに
android:searchSuggestAuthority
属性を追加して、コンテンツ プロバイダの名前空間をシステムに伝えます。AndroidManifest.xml
ファイル内の <provider>
要素の android:authorities
属性で指定した文字列値と一致する必要があります。
label(アプリケーションの名前)も含めます。システム検索設定は、検索可能なアプリを列挙するときにこのラベルを使用します。
また、カスタム候補を提供するインテント アクションを定義するために、searchable.xml
ファイルには値 "android.intent.action.VIEW"
を持つ
android:searchSuggestIntentAction
を含める必要があります。これは、次のセクションで説明するように、検索キーワードを提供するインテント アクションとは異なります。候補のインテント アクションを宣言するその他の方法については、インテント アクションを宣言するをご覧ください。
アプリは、インテントのアクションとともに、
android:searchSuggestIntentData
属性で指定するインテント データを提供する必要があります。これは、コンテンツを指す URI の最初の部分です。これは、そのコンテンツのマッピング テーブルのすべての行に共通する URI の部分を表します。列の識別セクションで説明されているように、URI の各行に固有の部分は、SUGGEST_COLUMN_INTENT_DATA_ID
フィールドで設定されます。候補のインテント データを宣言するその他の方法については、インテント データを宣言するをご覧ください。
android:searchSuggestSelection=" ?"
属性は、query()
メソッドの selection
パラメータとして渡される値を指定します。疑問符(?
)の値はクエリテキストに置き換えられます。
最後に、"true"
値を指定した
android:includeInGlobalSearch
属性も含める必要があります。searchable.xml
ファイルの例を次に示します。
<searchable xmlns:android="http://schemas.android.com/apk/res/android" android:label="@string/search_label" android:hint="@string/search_hint" android:searchSettingsDescription="@string/settings_description" android:searchSuggestAuthority="com.example.android.tvleanback" android:searchSuggestIntentAction="android.intent.action.VIEW" android:searchSuggestIntentData="content://com.example.android.tvleanback/video_database_leanback" android:searchSuggestSelection=" ?" android:searchSuggestThreshold="1" android:includeInGlobalSearch="true"> </searchable>
検索キーワードを処理する
列を特定するセクションで説明したように、アプリの列のいずれかの値に一致する単語が検索ダイアログに表示されると、すぐにシステムが ACTION_SEARCH
インテントを起動します。そのインテントを処理するアプリのアクティビティは、値に指定された単語を含む列をリポジトリで検索し、それらの列を含むコンテンツ アイテムのリストを返します。次の例に示すように、AndroidManifest.xml
ファイルで、ACTION_SEARCH
インテントを処理するアクティビティを指定します。
... <activity android:name="com.example.android.tvleanback.DetailsActivity" android:exported="true"> <!-- Receives the search request. --> <intent-filter> <action android:name="android.intent.action.SEARCH" /> <!-- No category needed, because the Intent will specify this class component --> </intent-filter> <!-- Points to searchable meta data. --> <meta-data android:name="android.app.searchable" android:resource="@xml/searchable" /> </activity> ... <!-- Provides search suggestions for keywords against video meta data. --> <provider android:name="com.example.android.tvleanback.VideoContentProvider" android:authorities="com.example.android.tvleanback" android:exported="true" /> ...
また、アクティビティでは、searchable.xml
ファイルへの参照を含む検索可能な構成を記述する必要があります。グローバル検索ダイアログを使用するには、検索クエリを受け取るアクティビティをマニフェストで記述する必要があります。また、マニフェストでは、searchable.xml
ファイルに記述されているのとまったく同じ <provider>
要素も記述する必要があります。
詳細画面におけるアプリへのディープリンク
検索候補を処理するセクションの説明に沿って検索構成をセットアップし、列を特定するセクションの説明に沿って SUGGEST_COLUMN_TEXT_1
、SUGGEST_COLUMN_PRODUCTION_YEAR
、SUGGEST_COLUMN_DURATION
フィールドをマッピングした場合、ユーザーが検索結果を選択したときに起動する詳細画面に、コンテンツの視聴アクションへの
ディープリンクが表示されます。
ユーザーが詳細画面の [**Available On**] ボタンによって識別されるアプリのリンクを選択すると、システムは、searchable.xml
ファイル内の値 "android.intent.action.VIEW"
で
android:searchSuggestIntentAction
に設定された ACTION_VIEW
を処理するアクティビティを起動します。
カスタム インテントを設定してアクティビティを起動することもできます。これは、
Leanback サンプルアプリで実証されています。サンプルアプリは独自の LeanbackDetailsFragment
を起動して、選択したメディアの詳細を表示します。アプリ内で、メディアをすぐに再生するアクティビティを起動して、ユーザーがあと 1 ~ 2 回クリックする手間を省きます。
検索の動作
Android TV では、ホーム画面とアプリ内から検索できます。この 2 つの場合、検索結果は異なります。
ホーム画面から検索する
ユーザーがホーム画面から検索すると、最初の結果がエンティティ カードに表示されます。コンテンツを再生できるアプリがある場合は、カードの下部に各アプリへのリンクが表示されます。
エンティティ カードにプログラムでアプリを配置することはできません。アプリの検索結果が、検索したコンテンツのタイトル、年、期間と一致していなければなりません。
カードの下にその他の検索結果が表示されることもあります。確認するには、リモコンを押し下げてスクロールする必要があります。各アプリの結果が別々の行に表示されます。行の順序は制御できません。ウォッチ アクションをサポートするアプリが最初に表示されます。
アプリから検索する
ユーザーは、リモコンまたはゲームパッド コントローラからマイクを起動して、アプリ内から検索を開始することもできます。検索結果は、アプリのコンテンツ上に 1 行で表示されます。アプリは、固有のグローバル検索プロバイダを使用して、検索結果を生成します。
詳細
TV アプリの検索について詳しくは、Android の検索機能をアプリに統合すると検索機能を追加するをご覧ください。
SearchFragment
を使用してアプリ内検索エクスペリエンスをカスタマイズする方法について詳しくは、TV アプリ内の検索をご覧ください。