讓 TV 應用程式可供搜尋

Android TV 使用 Android 搜尋介面 從已安裝的應用程式擷取內容資料,並將搜尋結果提供給使用者。應用程式的 結果中可以包含內容資料,讓使用者立即存取

您的應用程式必須提供資料欄位給 Android TV,讓 Android TV 根據這些欄位產生建議搜尋 。為此,您的應用程式必須實作 放送的內容供應器 以及 描述內容的 searchable.xml 設定檔 以及其他重要資訊。您也需要一個活動來處理 在使用者選取建議搜尋結果時觸發的意圖。適用對象 詳情請參閱新增 自訂搜尋建議。本指南涵蓋 Android TV 應用程式特有的重點。

閱讀本指南之前,請務必詳閱 Search API 指南。 另請參閱「新增搜尋功能」。

本指南中的程式碼範例來自 Leanback 範例應用程式 ,直接在 Google Cloud 控制台實際操作。

識別資料欄

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 媒體的時間長度,以毫秒為單位 (必要)

搜尋架構需要下列資料欄:

當內容這些欄的值符合其他相同內容的值時 系統會提供 深層連結到您的應用程式 查看內容,以及其他供應商的應用程式連結。這部分在 「在詳細資料畫面中的應用程式深層連結」部分。

應用程式的資料庫類別可能會定義下列資料欄:

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 的最後部分,說明內容儲存的位置。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: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 檔案中,納入 android:searchSuggestAuthority 屬性,用來向系統告知 內容供應器。這必須符合您在 android:authorities敬上 <provider> 的屬性 元素。AndroidManifest.xml

另包括標籤 也就是應用程式的名稱系統在列舉項目時,系統搜尋設定會使用這個標籤 。

searchable.xml 檔案 必須同時包含android:searchSuggestIntentAction 值設為 "android.intent.action.VIEW" 定義提供自訂建議的意圖動作。這與輸入意圖不同 動作來提供搜尋字詞,如下一節所述。 如需宣告建議意圖動作的其他方式, 請參閱宣告 意圖動作

除了意圖動作之外,應用程式必須提供意圖資料,而您希望使用 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_YEARSUGGEST_COLUMN_DURATION 欄位,如 「識別資料欄」部分、 深層連結:內容觀看動作的深層連結,會在啟動時顯示 使用者選取搜尋結果時:

詳細資料畫面上的深層連結

使用者選取應用程式連結時,系統會顯示「適用服務」按鈕 詳細資料畫面,系統會啟動處理 ACTION_VIEW 的活動 設為 android:searchSuggestIntentAction 的值設為 "android.intent.action.VIEW" searchable.xml 檔案。

您也可以設定自訂意圖來啟動活動。如 Leanback 範例應用程式 ,直接在 Google Cloud 控制台實際操作。請注意,範例應用程式會啟動自己的 LeanbackDetailsFragment, 顯示所選媒體的詳細資料;請啟動會播放媒體的活動 以便立即儲存使用者。

搜尋行為

從主畫面和應用程式內,可在 Android TV 中使用搜尋功能。搜尋結果 對於這兩個用途而言並不相同

在主畫面上搜尋

當使用者從主畫面進行搜尋時,第一筆搜尋結果會顯示在實體資訊卡中。如果有 可以播放內容的應用程式,資訊卡底部會顯示每個應用程式的連結:

電視搜尋結果播放

無法透過程式輔助方式將應用程式放入實體資訊卡。獲得 播放選項,應用程式的搜尋結果必須與節目的標題、年份和時間長度 搜尋內容

資訊卡下方可能會提供更多搜尋結果。如要查看這些通知,使用者必須按下 遠端設定並向下捲動畫面每個應用程式的結果會自成一列。就算您無法控制 資料列排序。支援應用程式 觀看動作清單會先列出。

電視搜尋結果

在應用程式中搜尋

使用者也可以透過遙控器啟動麥克風,從應用程式內開始搜尋 遊戲控制器控制器搜尋結果會顯示在應用程式內容頂端,並顯示為一列。 您的應用程式是運用自家的全域搜尋引擎產生搜尋結果。

電視應用程式內搜尋結果

瞭解詳情

如要進一步瞭解如何搜尋 TV 應用程式,請參閱 將 Android 搜尋功能整合至您的應用程式新增搜尋功能

如要進一步瞭解如何使用 SearchFragment 自訂應用程式內搜尋體驗,請參閱 在 TV 應用程式中搜尋