TV アプリを検索可能にする

Android TV は Android 検索インターフェースを使用します インストール済みのアプリからコンテンツ データを取得し、ユーザーに検索結果を提供します。アプリの コンテンツ データを結果に含めることで、ユーザーは 説明します。

アプリは、Android TV がサジェスト検索を生成できるデータ フィールドを Android TV に提供する必要があります ユーザーが検索ダイアログに文字を入力したときに表示されます。そのためには、アプリに 配信するコンテンツ プロバイダ と一緒に候補を表示する コンテンツを記述する searchable.xml 構成ファイル Android TV に関する重要な情報を提供します。また、イベントを処理するアクティビティも ユーザーが検索結果の候補を選択したときに起動されるインテントです。対象 詳しくは、 カスタム検索候補このガイドでは、Android TV アプリに固有の主なポイントについて説明します。

このガイドを読む前に、 Search API ガイドをご覧ください。 検索機能の追加についてもご確認ください。

このガイドのサンプルコードは、 <ph type="x-smartling-placeholder"></ph> Leanback サンプルアプリ をタップします。

列を特定する

SearchManager は、想定しているデータ フィールドを、次のように表現して記述します。 ローカル データベースの列です。データの形式にかかわらず、データ フィールドを 通常はコンテンツ データにアクセスするクラスにあります。Cloud Build を使用して 既存のデータを必須フィールドにマッピングするクラスについて詳しくは、以下をご覧ください。 <ph type="x-smartling-placeholder"></ph> 候補テーブルの作成をご覧ください。

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 Cloud の プロバイダが 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(): MapS<tring, 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 の一部です。 row - URI の最後の部分。コンテンツの保存場所を表します。URI の最初の部分は、 テーブル内のすべての行に共通で設定されている場合、 searchable.xml ファイルを <ph type="x-smartling-placeholder"></ph> 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:exported 属性を次のように設定する必要があります。 "true" を使用して、返された結果を Android グローバル検索で使用できるようにします。

<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 ファイルに以下を含めます。 <ph type="x-smartling-placeholder"></ph> android:searchSuggestAuthority 属性を使用して、名前空間をシステムに通知します。 コンテンツ プロバイダを指定します。これは android:authorities <provider> の属性 要素を AndroidManifest.xml ファイルに追加します。

ラベルも含めます。 アプリケーションの名前です。システム検索の設定では、このラベルが 検索可能なアプリを表示します。

searchable.xml ファイル も含める必要があります。 android:searchSuggestIntentAction"android.intent.action.VIEW" カスタム候補を提供するためのインテントのアクションを定義します。これは 検索キーワードを指定するアクション。次のセクションで説明します。 提案のインテント アクションを宣言するその他の方法については、 インテントのアクション

アプリはインテントのアクションとともに、 <ph type="x-smartling-placeholder"></ph> android:searchSuggestIntentData 属性。これは URI の最初の部分で、 これは、そのコンテンツに対するマッピング テーブル内のすべての行に共通する URI の部分を 説明します。URI の各行に固有の部分は、SUGGEST_COLUMN_INTENT_DATA_ID フィールドで確立されます。 列を特定するセクションの説明に従います。 候補のインテント データを宣言するその他の方法については、以下をご覧ください。 宣言 インテント データ

android:searchSuggestSelection=" ?" 属性は、渡される値を指定します。 query()selection パラメータとして メソッドを呼び出します。疑問符(?)の値がクエリテキストに置き換えられます。

最後に、 android:includeInGlobalSearch 属性を値 "true" にします。例を示します。 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 ファイル。 グローバル検索ダイアログを使用するには: マニフェストでは、検索クエリを受け取るアクティビティを記述する必要があります。また、マニフェストでは、 <provider> 要素で、searchable.xml ファイルの説明とまったく同じである必要があります。

詳細画面におけるアプリへのディープリンク

検索の処理方法 セクションを作成し、SUGGEST_COLUMN_TEXT_1 SUGGEST_COLUMN_PRODUCTION_YEAR、および 説明のとおり、SUGGEST_COLUMN_DURATION フィールド [列を特定] セクションにある コンテンツの視聴アクションへのディープリンクが、 ユーザーが検索結果を選択したとき:

詳細画面におけるディープリンク

ユーザーがアプリのリンクを選択すると、 詳細画面が表示されると、ACTION_VIEW を処理するアクティビティが起動します。 「」に設定 値 "android.intent.action.VIEW" を含む android:searchSuggestIntentAction searchable.xml ファイル。

カスタム インテントを設定してアクティビティを起動することもできます。これについては <ph type="x-smartling-placeholder"></ph> Leanback サンプルアプリ をタップします。なお、サンプルアプリは自身の LeanbackDetailsFragment を起動して、 選択したメディアの詳細を表示メディアを再生するアクティビティを起動する クリックの手間が省けます

検索の動作

検索は Android TV のホーム画面とアプリ内から利用できます。検索結果 この 2 つのケースでは異なります

ホーム画面から検索する

ユーザーがホーム画面から検索すると、最初の結果がエンティティ カードに表示されます。もし コンテンツを再生できるアプリがある場合、それぞれのリンクがカードの下部に表示されます。

TV 検索結果の再生

プログラムでアプリをエンティティ カードに配置することはできません。含まれるもの 再生オプションを設定する場合、アプリの検索結果は、アプリのタイトル、年、再生時間と一致する必要があります。 表示されます。

カードの下には、その他の検索結果が表示される場合があります。確認するには、画面上の 下にスクロールします。各アプリの結果が個別の行に表示されます。kubectl の 行の順序付けを行います対応アプリ ウォッチ アクションが最初に表示されます。

テレビの検索結果

アプリから検索する

ユーザーは、リモコンのマイクや、 ゲームパッド コントローラ。検索結果は、アプリのコンテンツの上に 1 行で表示されます。 アプリは、固有のグローバル検索プロバイダを使用して、検索結果を生成します。

テレビのアプリ内検索結果

詳細

TV アプリの検索について詳しくは、以下をご覧ください。 Android 検索機能をアプリに統合する 検索機能を追加する

SearchFragment を使用してアプリ内検索エクスペリエンスをカスタマイズする方法について詳しくは、以下をご覧ください。 TV アプリ内で検索する