Rendi disponibili le app per la TV

Android TV utilizza l'interfaccia di ricerca di Android per recuperare i dati sui contenuti dalle app installate e mostrare i risultati di ricerca all'utente. Della tua app i dati dei contenuti possono essere inclusi in questi risultati per dare all'utente l'accesso immediato ai contenuti in la tua app.

La tua app deve fornire ad Android TV i campi di dati da cui Android TV può generare la ricerca suggerita risultati pertinenti mentre l'utente inserisce i caratteri nella finestra di dialogo di ricerca. Per farlo, la tua app deve implementare una Fornitore di contenuti che offre i suggerimenti insieme a searchable.xml che descrive i contenuti e altre informazioni essenziali per Android TV. È necessaria anche un'attività che gestisca le per intenzione che si attiva quando l'utente seleziona un risultato di ricerca suggerito. Per per altri dettagli, consulta l'articolo Aggiungere suggerimenti di ricerca personalizzati. Questa guida illustra i punti principali specifici delle app Android TV.

Prima di leggere questa guida, è necessario acquisire familiarità con i concetti spiegati nella Guida dell'API Search. Inoltre, consulta l'articolo Aggiungere funzionalità di ricerca.

Il codice campione di questa guida proviene Esempio di app Leanback di Google.

Identifica le colonne

L'SearchManager descrive i campi di dati che si aspetta rappresentandoli come di un database locale. Indipendentemente dal formato dei dati, devi mappare i campi dati a queste colonne, di solito nella classe che accede ai dati dei contenuti. Per informazioni su come creare per una classe che mappa i dati esistenti ai campi obbligatori, consulta Creazione di una tabella dei suggerimenti.

La classe SearchManager include diverse colonne per Android TV. Alcuni dei le colonne più importanti sono descritte nella seguente tabella.

Valore Descrizione
SUGGEST_COLUMN_TEXT_1 Il nome dei tuoi contenuti (obbligatorio)
SUGGEST_COLUMN_TEXT_2 Una descrizione testuale dei tuoi contenuti
SUGGEST_COLUMN_RESULT_CARD_IMAGE Un'immagine, un poster o una copertina per i tuoi contenuti
SUGGEST_COLUMN_CONTENT_TYPE Il tipo MIME dei contenuti multimediali
SUGGEST_COLUMN_VIDEO_WIDTH La larghezza della risoluzione dei contenuti multimediali
SUGGEST_COLUMN_VIDEO_HEIGHT L'altezza di risoluzione dei contenuti multimediali
SUGGEST_COLUMN_PRODUCTION_YEAR L'anno di produzione dei tuoi contenuti (obbligatorio)
SUGGEST_COLUMN_DURATION La durata in millisecondi dei tuoi contenuti multimediali (obbligatorio)

Il framework di ricerca richiede le seguenti colonne:

Quando i valori di queste colonne per i tuoi contenuti corrispondono ai valori per gli stessi contenuti di altri trovati dai server di Google, il sistema fornisce Un link diretto alla tua app nei dettagli visualizzazione per i contenuti, insieme ai link alle app di altri fornitori. Questo argomento verrà discusso più in dettaglio nella Vai alla sezione Link diretto all'app nella schermata dei dettagli.

La classe del database della tua applicazione potrebbe definire le colonne come segue:

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

Quando crei la mappa dalle colonne SearchManager ai campi di dati, Devi inoltre specificare il valore _ID per assegnare un ID univoco a ogni riga.

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

Nell'esempio precedente, nota la mappatura alla SUGGEST_COLUMN_INTENT_DATA_ID . Si tratta della parte dell'URI che punta ai contenuti univoci per i dati in questo riga: l'ultima parte dell'URI, che descrive dove sono archiviati i contenuti. La prima parte dell'URI, quando è comune a tutte le righe della tabella, viene impostato nel searchable.xml come file android:searchSuggestIntentData, come descritto nell' Gestire i suggerimenti di ricerca.

Se la prima parte dell'URI è diversa per ogni riga nella mappa quel valore con il campo SUGGEST_COLUMN_INTENT_DATA. Quando l'utente seleziona questi contenuti, l'intent che si attiva fornisce i dati sull'intent provenienti combinazione di SUGGEST_COLUMN_INTENT_DATA_ID e l'attributo android:searchSuggestIntentData o Valore del campo SUGGEST_COLUMN_INTENT_DATA.

Fornisci dati sui suggerimenti di ricerca

Implementare un fornitore di contenuti per restituire i suggerimenti per i termini di ricerca nella finestra di dialogo di ricerca di Android TV. Il sistema interroga i tuoi contenuti per ricevere suggerimenti chiamando il metodo query() ogni volta viene digitata una lettera. Nella tua implementazione di query(), i tuoi contenuti provider cerca i dati dei suggerimenti e restituisce un Cursor che rimanda a le righe che hai scelto per i suggerimenti.

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

Il fornitore di contenuti viene sottoposto a un trattamento speciale nel file manifest. Piuttosto che essere contrassegnata come un'attività, viene descritta come <provider> La Il provider include l'attributo android:authorities per indicare al sistema del tuo fornitore di contenuti. Inoltre, devi impostare l'attributo android:exported su "true" per consentire alla ricerca globale di Android di utilizzare i risultati restituiti.

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

Gestire i suggerimenti di ricerca

L'app deve includere un res/xml/searchable.xml per configurare le impostazioni dei suggerimenti di ricerca.

Nel file res/xml/searchable.xml, includi android:searchSuggestAuthority per indicare al sistema lo spazio dei nomi del tuo fornitore di contenuti. Deve corrispondere al valore stringa specificato nel android:authorities attributo di <provider> nel file AndroidManifest.xml.

Includi anche un'etichetta, che è il nome dell'applicazione. Le impostazioni di ricerca di sistema utilizzano questa etichetta durante l'enumerazione app disponibili per la ricerca.

Il file searchable.xml deve includere anche android:searchSuggestIntentAction con il valore "android.intent.action.VIEW" per definire l'azione intent per fornire un suggerimento personalizzato. Questo è diverso dall'intento per fornire un termine di ricerca, come descritto nella sezione che segue. Per altri modi per dichiarare l'azione intent per i suggerimenti, consulta la sezione Dichiarazione dei azione intent.

Insieme all'azione per intent, la tua app deve fornire i dati sull'intent, che specifichi android:searchSuggestIntentData. Questa è la prima parte dell'URI che punta ai contenuti, che descrive la parte dell'URI comune a tutte le righe della tabella di mappatura contenuti. La porzione dell'URI univoca per ogni riga viene stabilita con il campo SUGGEST_COLUMN_INTENT_DATA_ID, come descritto nella sezione Identifica le colonne. Per altri modi per dichiarare i dati sull'intent per i suggerimenti, consulta Dichiarazione i dati sull'intent.

L'attributo android:searchSuggestSelection=" ?" specifica il valore trasmesso come parametro selection di query() . Il valore del punto interrogativo (?) viene sostituito con il testo della query.

Infine, devi includere anche android:includeInGlobalSearch con il valore "true". Ecco un esempio File 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>

Gestire i termini di ricerca

Non appena la finestra di dialogo di ricerca contiene una parola corrispondente al valore in una delle colonne dell'app, descritto nella sezione Identificare le colonne, il sistema attiva il Intent ACTION_SEARCH. L'attività nella tua app che gestisce l'intent cerca nel repository colonne con la parola specificata nei valori e restituisce un elenco di contenuti con quelle colonne. Nel file AndroidManifest.xml, specifichi attività che gestisce ACTION_SEARCH come mostrato nell'esempio seguente:

...
  <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'attività deve anche descrivere la configurazione disponibile per la ricerca con un riferimento ai searchable.xml. Per utilizzare la finestra di dialogo di ricerca globale: il file manifest deve descrivere l'attività che deve ricevere le query di ricerca. Il file manifest deve inoltre descrivere il <provider> , esattamente come descritto nel file searchable.xml.

Link diretto all'app nella schermata dei dettagli

Se hai impostato la configurazione di ricerca come descritto nella sezione Gestire la ricerca suggerimenti e ha mappato SUGGEST_COLUMN_TEXT_1, SUGGEST_COLUMN_PRODUCTION_YEAR e SUGGEST_COLUMN_DURATION campi come descritto in la sezione Identifica colonne, una Il link diretto a un'azione di visualizzazione per i tuoi contenuti viene visualizzato nella schermata dei dettagli che viene avviata quando l'utente seleziona un risultato di ricerca:

Link diretto nella schermata dei dettagli

Quando l'utente seleziona il link per la tua app, identificato dal pulsante **Disponibile su** nella schermata dei dettagli, il sistema avvia l'attività che gestisce ACTION_VIEW imposta come android:searchSuggestIntentAction con il valore "android.intent.action.VIEW" in il file searchable.xml.

Puoi anche configurare un intent personalizzato per avviare la tua attività. Ciò è dimostrato nel Esempio di app Leanback di Google. Tieni presente che l'app di esempio avvia il proprio LeanbackDetailsFragment in mostra i dettagli dei contenuti multimediali selezionati; nelle tue app, avvia l'attività che riproduce i contenuti multimediali immediatamente per salvare l'utente altri due clic.

Comportamento di ricerca

La ricerca è disponibile su Android TV dalla schermata Home e dall'interno dell'app. Risultati di ricerca sono diverse per questi due casi.

Esegui ricerche dalla schermata Home

Quando l'utente esegue una ricerca dalla schermata Home, il primo risultato viene visualizzato in una scheda dell'entità. Se ci sono di app in grado di riprodurre i contenuti, nella parte inferiore della scheda viene visualizzato un link per ognuna:

Riproduzione dei risultati di ricerca TV

Non puoi inserire in modo programmatico un'app nella scheda dell'entità. Da includere come l'opzione di riproduzione, i risultati di ricerca di un'app devono corrispondere al titolo, all'anno e alla durata dei contenuti cercati.

Sotto la scheda potrebbero essere disponibili altri risultati di ricerca. Per vederle, l'utente deve premere il pulsante telecomando e scorri verso il basso. I risultati per ogni app vengono visualizzati in una riga separata. Non puoi controllare nell'ordinamento delle righe. App che supportano azioni dell'orologio sono elencate per prime.

Risultati di ricerca TV

Cerca dall'app

L'utente può anche avviare una ricerca dall'interno dell'app avviando il microfono dal telecomando oppure un controller per gamepad. I risultati di ricerca vengono visualizzati in una singola riga sopra i contenuti dell'app. La tua app genera risultati di ricerca usando il suo provider di ricerca globale.

Risultati di ricerca in-app TV

Scopri di più

Per scoprire di più sulla ricerca di un'app TV, leggi Integra le funzionalità di ricerca di Android nella tua app e Aggiungi la funzionalità di ricerca.

Per ulteriori informazioni su come personalizzare l'esperienza di ricerca in-app con un SearchFragment, leggi Eseguire ricerche nelle app TV.