Permettre les recherches dans les applications TV

Android TV utilise l'interface de recherche Android pour récupérer les données de contenu des applications installées et fournir des résultats de recherche à l'utilisateur. Les données de contenu de votre application peuvent être incluses dans ces résultats pour permettre à l'utilisateur d'accéder instantanément au contenu de votre application.

Votre application doit fournir à Android TV les champs de données à partir desquels elle peut générer des résultats de recherche suggérés lorsque l'utilisateur saisit des caractères dans la boîte de dialogue de recherche. Pour ce faire, votre application doit implémenter un fournisseur de contenu qui renvoie les suggestions, ainsi qu'un fichier de configuration searchable.xml décrivant le fournisseur de contenu et d'autres informations essentielles pour Android TV. Vous avez également besoin d'une activité qui gère l'intent qui se déclenche lorsque l'utilisateur sélectionne un résultat de recherche suggéré. Pour en savoir plus, consultez Ajouter des suggestions de recherche personnalisées. Ce guide décrit les points principaux spécifiques aux applications Android TV.

Avant de lire ce guide, assurez-vous de connaître les concepts décrits dans le guide de l'API Search. Consultez également Ajouter une fonctionnalité de recherche.

L'exemple de code présenté dans ce guide provient de l' application exemple Leanback.

Identifier les colonnes

SearchManager décrit les champs de données attendus en les représentant en tant que colonnes d'une base de données locale. Quel que soit le format de vos données, vous devez mapper vos champs de données à ces colonnes, généralement dans la classe qui accède à vos données de contenu. Pour savoir comment créer une classe qui mappe vos données existantes aux champs obligatoires, consultez Créer une table de suggestions.

La classe SearchManager comprend plusieurs colonnes pour Android TV. Certaines des colonnes les plus importantes sont décrites dans le tableau suivant.

Valeur Description
SUGGEST_COLUMN_TEXT_1 Nom de votre contenu (obligatoire)
SUGGEST_COLUMN_TEXT_2 Une description textuelle de votre contenu
SUGGEST_COLUMN_RESULT_CARD_IMAGE Image, affiche ou couverture de votre contenu
SUGGEST_COLUMN_CONTENT_TYPE Type MIME de votre média
SUGGEST_COLUMN_VIDEO_WIDTH Largeur de la résolution de votre contenu multimédia
SUGGEST_COLUMN_VIDEO_HEIGHT Hauteur de résolution de votre contenu multimédia
SUGGEST_COLUMN_PRODUCTION_YEAR L'année de production de votre contenu (obligatoire)
SUGGEST_COLUMN_DURATION Durée de votre contenu multimédia, en millisecondes (obligatoire)

Le framework de recherche nécessite les colonnes suivantes:

Lorsque les valeurs de ces colonnes pour votre contenu correspondent aux valeurs du même contenu provenant d'autres fournisseurs trouvés par les serveurs Google, le système fournit un lien profond vers votre application dans la vue détaillée du contenu, ainsi que des liens vers les applications d'autres fournisseurs. Pour en savoir plus, consultez la section Lien profond vers votre application sur l'écran d'informations.

La classe de base de données de votre application peut définir les colonnes comme suit:

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;
...

Lorsque vous créez la correspondance à partir des colonnes SearchManager avec vos champs de données, vous devez également spécifier le _ID pour attribuer un ID unique à chaque ligne.

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;
  }
...

Dans l'exemple précédent, notez le mappage avec le champ SUGGEST_COLUMN_INTENT_DATA_ID. Il s'agit de la partie de l'URI qui pointe vers le contenu propre aux données de cette ligne. Il s'agit de la dernière partie de l'URI, qui décrit l'emplacement de stockage du contenu. La première partie de l'URI, lorsqu'elle est commune à toutes les lignes de la table, est définie dans le fichier searchable.xml en tant qu'attribut android:searchSuggestIntentData, comme décrit dans la section Gérer les suggestions de recherche.

Si la première partie de l'URI est différente pour chaque ligne de la table, mappez cette valeur avec le champ SUGGEST_COLUMN_INTENT_DATA. Lorsque l'utilisateur sélectionne ce contenu, l'intent qui se déclenche fournit les données d'intent à partir de la combinaison de SUGGEST_COLUMN_INTENT_DATA_ID et de l'attribut android:searchSuggestIntentData ou de la valeur du champ SUGGEST_COLUMN_INTENT_DATA.

Fournir des données de suggestion de recherche

Implémentez un fournisseur de contenu pour renvoyer des suggestions de termes de recherche dans la boîte de dialogue de recherche d'Android TV. Le système interroge votre fournisseur de contenu pour obtenir des suggestions en appelant la méthode query() chaque fois qu'une lettre est saisie. Dans votre implémentation de query(), votre fournisseur de contenu recherche vos données de suggestion et renvoie un Cursor qui pointe vers les lignes que vous avez désignées pour des suggestions.

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);
}
...

Dans votre fichier manifeste, le fournisseur de contenu fait l'objet d'un traitement spécial. Plutôt que d'être taguée en tant qu'activité, elle est décrite comme une <provider>. Le fournisseur inclut l'attribut android:authorities pour indiquer au système l'espace de noms de votre fournisseur de contenu. Vous devez également définir son attribut android:exported sur "true" afin que la recherche globale Android puisse utiliser les résultats renvoyés.

<provider android:name="com.example.android.tvleanback.VideoContentProvider"
    android:authorities="com.example.android.tvleanback"
    android:exported="true" />

Gérer les suggestions de recherche

Votre application doit inclure un fichier res/xml/searchable.xml pour configurer les paramètres des suggestions de recherche.

Dans le fichier res/xml/searchable.xml, incluez l'attribut android:searchSuggestAuthority pour indiquer au système l'espace de noms de votre fournisseur de contenu. Elle doit correspondre à la valeur de chaîne que vous spécifiez dans l'attribut android:authorities de l'élément <provider> de votre fichier AndroidManifest.xml.

Incluez également un libellé, qui correspond au nom de l'application. Les paramètres de recherche du système utilisent ce libellé pour énumérer les applications pouvant faire l'objet d'une recherche.

Le fichier searchable.xml doit également inclure android:searchSuggestIntentAction avec la valeur "android.intent.action.VIEW" pour définir l'action d'intent permettant de fournir une suggestion personnalisée. Cette opération est différente de l'action d'intent pour fournir un terme de recherche, comme décrit dans la section suivante. Pour découvrir d'autres façons de déclarer l'action d'intent pour obtenir des suggestions, consultez la section Déclarer l'action d'intent.

Avec l'action d'intent, votre application doit fournir les données d'intent, que vous spécifiez avec l'attribut android:searchSuggestIntentData. Il s'agit de la première partie de l'URI qui pointe vers le contenu. Elle décrit la partie de l'URI commune à toutes les lignes de la table de mappage pour ce contenu. La partie de l'URI propre à chaque ligne est établie avec le champ SUGGEST_COLUMN_INTENT_DATA_ID, comme décrit dans la section Identifier les colonnes. Pour découvrir d'autres façons de déclarer des données d'intent afin d'obtenir des suggestions, consultez la section Déclarer les données d'intent.

L'attribut android:searchSuggestSelection=" ?" spécifie la valeur transmise en tant que paramètre selection de la méthode query(). La valeur du point d'interrogation (?) est remplacée par le texte de la requête.

Enfin, vous devez également inclure l'attribut android:includeInGlobalSearch avec la valeur "true". Voici un exemple de fichier 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>

Gérer les termes de recherche

Dès que la boîte de dialogue de recherche contient un mot qui correspond à la valeur de l'une des colonnes de votre application, comme décrit dans la section Identifier les colonnes, le système déclenche l'intent ACTION_SEARCH. L'activité de votre application qui gère cet intent recherche dans le dépôt les colonnes contenant le mot donné dans leurs valeurs, puis renvoie une liste d'éléments de contenu avec ces colonnes. Dans votre fichier AndroidManifest.xml, vous désignez l'activité qui gère l'intent ACTION_SEARCH, comme indiqué dans l'exemple suivant :

...
  <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" />
...

L'activité doit également décrire la configuration incluse dans l'index de recherche en faisant référence au fichier searchable.xml. Pour utiliser la boîte de dialogue de recherche globale, le fichier manifeste doit décrire l'activité qui doit recevoir les requêtes de recherche. Le fichier manifeste doit également décrire l'élément <provider> exactement tel qu'il est décrit dans le fichier searchable.xml.

Lien profond vers votre appli sur l'écran d'informations

Si vous avez configuré la configuration de recherche comme décrit dans la section Gérer les suggestions de recherche et mappé les champs SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_PRODUCTION_YEAR et SUGGEST_COLUMN_DURATION comme décrit dans la section Identifier les colonnes, un lien profond vers une action de visionnage pour votre contenu apparaît dans l'écran d'informations qui s'ouvre lorsque l'utilisateur sélectionne un résultat de recherche:

Lien profond sur l&#39;écran d&#39;informations

Lorsque l'utilisateur sélectionne le lien de votre application (identifié par le bouton **Disponible le** sur l'écran de détails), le système lance l'activité qui gère l'élément ACTION_VIEW défini sur android:searchSuggestIntentAction avec la valeur "android.intent.action.VIEW" dans le fichier searchable.xml.

Vous pouvez également configurer un intent personnalisé pour lancer votre activité. Cela est illustré dans l' application exemple Leanback. Notez que l'application exemple lance son propre LeanbackDetailsFragment pour afficher les détails du contenu multimédia sélectionné. Dans vos applications, lancez immédiatement l'activité qui lit le contenu multimédia pour enregistrer un ou deux clics de l'utilisateur.

Comportement de recherche

La recherche est disponible sur Android TV depuis l'écran d'accueil et dans l'application. Les résultats de recherche sont différents dans ces deux cas.

Rechercher depuis l'écran d'accueil

Lorsque l'utilisateur effectue une recherche depuis l'écran d'accueil, le premier résultat apparaît dans une fiche d'entité. Si des applications peuvent lire le contenu, un lien vers chacune d'elles s'affiche au bas de la fiche:

Lecture des résultats de recherche TV

Vous ne pouvez pas placer une application dans la fiche d'entité par programmation. Pour être incluses en tant qu'option de lecture, les résultats de recherche d'une application doivent correspondre au titre, à l'année et à la durée du contenu recherché.

D'autres résultats de recherche peuvent s'afficher sous la fiche. Pour les voir, l'utilisateur doit appuyer sur la télécommande et faire défiler la page vers le bas. Les résultats pour chaque application s'affichent sur une ligne distincte. Vous ne pouvez pas contrôler l'ordre des lignes. Les applications qui acceptent les actions de visionnage sont listées en premier.

Résultats de recherche TV

Rechercher depuis votre application

L'utilisateur peut également lancer une recherche depuis votre application en lançant le micro à partir de la télécommande ou de la manette de jeu. Les résultats de recherche sont affichés sur une seule ligne au-dessus du contenu de l'application. Votre application génère des résultats de recherche à l'aide de son propre moteur de recherche global.

Résultats de recherche TV dans l&#39;application

En savoir plus

Pour en savoir plus sur la recherche dans une application TV, consultez Intégrer les fonctionnalités de recherche Android dans votre application et Ajouter une fonctionnalité de recherche.

Pour savoir comment personnaliser l'expérience de recherche dans l'application avec un SearchFragment, consultez Rechercher dans les applications TV.