The Android Developer Challenge is back! Submit your idea before December 2.

Canales en la pantalla principal

La pantalla principal de Android TV, o simplemente pantalla principal, proporciona una IU que muestra el contenido recomendado en una tabla de canales y programas. Cada fila corresponde a un canal. Un canal, a su vez, tiene tarjetas para cada uno de sus programas:

Pantalla principal de TV

En este documento, se muestra cómo agregar canales y programas a la pantalla principal, actualizar contenido, controlar las acciones del usuario y proporcionar la mejor experiencia posible para todos los usuarios. (Si quieres indagar más en la API, prueba el codelab de la pantalla principal y mira la sesión de Android TV de I/O 2017).

Nota: Los canales de recomendaciones solo están disponibles en Android 8.0 (API nivel 26) y versiones posteriores. Debes usarlos si quieres proporcionar recomendaciones para las apps que se ejecutan en Android 8.0 (API nivel 26) y versiones posteriores. A fin de proporcionar recomendaciones para apps que se ejecutan en versiones anteriores de Android, tu app, en su lugar, debe usar la fila de recomendaciones.

IU de la pantalla principal

Las apps pueden crear nuevos canales, o bien agregar, quitar y actualizar programas en un canal, así como controlar el orden de los programas de un canal. Por ejemplo, una app puede crear un canal denominado "Novedades" y mostrar tarjetas para los nuevos programas disponibles.

Las apps no pueden controlar el orden en que se muestran los canales en la pantalla principal. Cuando tu app crea un nuevo canal, la pantalla principal lo agrega a la parte inferior de la lista de canales. El usuario puede reordenar, ocultar y mostrar los canales.

Canal "Ver a continuación"

El canal "Ver a continuación" es la segunda fila que se muestra en la pantalla principal, después de la fila de apps. El sistema crea y mantiene este canal. Tu app puede agregar programas al canal "Ver a continuación", por ejemplo, aquellos que el usuario marcó como interesantes, los que dejó por la mitad o los relacionados con el contenido que está mirando (como el siguiente episodio de una serie o la próxima temporada de un programa).

El canal "Ver a continuación" tiene algunas restricciones: tu app no puede mover, quitar ni ocultar la fila este canal.

Canales de apps

Todos los canales que crea la app siguen este ciclo de vida:

  1. El usuario detecta un canal en la app y solicita agregarlo a la pantalla principal.
  2. La app crea el canal y lo agrega a TvProvider (en este punto, el canal no es visible).
  3. La app le solicita al sistema que muestre el canal.
  4. El sistema le solicita al usuario que apruebe el nuevo canal.
  5. El nuevo canal se muestra en la última fila de la pantalla principal.

Canal predeterminado

Tu app puede ofrecer cualquier número de canales para que el usuario los agregue a la pantalla principal. A fin de que estos aparezcan, el usuario debe seleccionar y aprobar cada uno. Cada app tiene la opción de crear un canal predeterminado. El canal predeterminado es especial porque se muestra automáticamente en la pantalla principal; el usuario no tiene que solicitarlo de forma explícita.

Requisitos previos

La pantalla principal de Android TV usa las API TvProvider de Android para administrar los canales y los programas que crea la app. Para acceder a los datos del proveedor, agrega el siguiente permiso al manifiesto de la app:

<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
    

La biblioteca de compatibilidad de TvProvider facilita el uso del proveedor. Agrégala a las dependencias en el archivo build.gradle:

compile 'com.android.support:support-tv-provider:27.0.0'
    

Para trabajar con canales y programas, asegúrate de incluir estas importaciones de la biblioteca de compatibilidad en tu programa:

Kotlin

    import android.support.media.tv.Channel
    import android.support.media.tv.TvContractCompat
    import android.support.media.tv.ChannelLogoUtils
    import android.support.media.tv.PreviewProgram
    import android.support.media.tv.WatchNextProgram
    

Java

    import android.support.media.tv.Channel;
    import android.support.media.tv.TvContractCompat;
    import android.support.media.tv.ChannelLogoUtils;
    import android.support.media.tv.PreviewProgram;
    import android.support.media.tv.WatchNextProgram;
    

Canales

El primer canal que crea tu app se convierte en el canal predeterminado. El canal predeterminado se muestra automáticamente en la pantalla principal. El usuario debe seleccionar y aceptar todos los demás canales que crees para que estos se muestren en la pantalla principal.

Cómo crear un canal

La app debe solicitar al sistema que muestre los últimos canales agregados solo cuando se ejecuta en segundo plano. Esto evita que la app muestre un diálogo solicitando la aprobación para agregar el canal mientras el usuario está ejecutando una app diferente. Si intentas agregar un canal mientras se ejecuta en segundo plano, el método onActivityResult() de la actividad muestra el código de estado RESULT_CANCELED.

Para crear un canal, sigue estos pasos:

  1. Diseña un creador de canales y establece sus atributos. Ten en cuenta que el tipo de canal debe ser TYPE_PREVIEW. Agrega más atributos según sea necesario.

    Kotlin

        val builder = Channel.Builder()
        // Every channel you create must have the type TYPE_PREVIEW
        builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
                .setDisplayName("Channel Name")
                .setAppLinkIntentUri(uri)
        

    Java

        Channel.Builder builder = new Channel.Builder();
        // Every channel you create must have the type TYPE_PREVIEW
        builder.setType(TvContractCompat.Channels.TYPE_PREVIEW)
                .setDisplayName("Channel Name")
                .setAppLinkIntentUri(uri);
        
  2. Inserta el canal en el proveedor:

    Kotlin

        var channelUri = context.contentResolver.insert(
                TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues())
        

    Java

        Uri channelUri = context.getContentResolver().insert(
                TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues());
        
  3. Debes guardar el ID de canal en orden para agregar programas al canal más tarde. Extrae el ID de canal de la URI que se muestra:

    Kotlin

        var channelId = ContentUris.parseId(channelUri)
        

    Java

        long channelId = ContentUris.parseId(channelUri);
        
  4. Debes agregar un logotipo para el canal. Usa Uri o Bitmap. El icono del logotipo debe ser opaco y de 80 dp x 80 dp. Se mostrará con una máscara circular:

    Máscara de ícono de pantalla principal de TV

    Kotlin

        // Choose one or the other
        storeChannelLogo(context: Context, channelId: Long, logoUri: Uri) // also works if logoUri is a URL
        storeChannelLogo(context: Context, channelId: Long, logo: Bitmap)
        

    Java

        // Choose one or the other
        storeChannelLogo(Context context, long channelId, Uri logoUri); // also works if logoUri is a URL
        storeChannelLogo(Context context, long channelId, Bitmap logo);
        
  5. Crea el canal predeterminado (opcional): Cuando la app crea el primer canal, puedes convertirlo en el canal predeterminado para que se muestre en la pantalla principal de inmediato sin necesidad de ninguna acción del usuario. El resto de los canales que creas no se muestran hasta que el usuario los selecciona de forma explícita.

    Kotlin

        TvContractCompat.requestChannelBrowsable(context, channelId)
        

    Java

        TvContractCompat.requestChannelBrowsable(context, channelId);
        

  6. Puedes hacer que el canal predeterminado se muestre antes de que el usuario abra la app. Para definir este comportamiento, agrega una instancia de BroadcastReceiver que escuche la acción android.media.tv.action.INITIALIZE_PROGRAMS, que la pantalla principal envía una vez que se instaló la app:
        <receiver
          android:name=".RunOnInstallReceiver"
          android:exported="true">
            <intent-filter>
              <action android:name="android.media.tv.action.INITIALIZE_PROGRAMS" />
              <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </receiver>
        
    Si transfieres tu app durante el desarrollo, para probar este paso, puedes activar el intent mediante adb, donde your.package.name/.YourReceiverName es el BroadcastReceiver de tu app:

        adb shell am broadcast -a android.media.tv.action.INITIALIZE_PROGRAMS -n \
            your.package.name/.YourReceiverName
        

    En unos pocos casos, es posible que tu app reciba la emisión al mismo tiempo que el usuario inicia tu app. Asegúrate de que el código no intente agregar el canal predeterminado más de una vez.

Cómo actualizar un canal

Actualizar canales es muy similar a crearlos.

Usa otra instancia de Channel.Builder para definir los atributos que se deben cambiar.

Usa ContentResolver para actualizar el canal. Usa el ID de canal que guardaste cuando se agregó el canal originalmente:

Kotlin

    context.contentResolver.update(
            TvContractCompat.buildChannelUri(channelId),
            builder.build().toContentValues(),
            null,
            null
    )
    

Java

    context.getContentResolver().update(TvContractCompat.buildChannelUri(channelId),
        builder.build().toContentValues(), null, null);
    

Para actualizar un logotipo de canal, usa storeChannelLogo().

Cómo borrar un canal

Kotlin

    context.contentResolver.delete(TvContractCompat.buildChannelUri(channelId), null, null)
    

Java

    context.getContentResolver().delete(TvContractCompat.buildChannelUri(channelId), null, null);
    

Programas

Cómo agregar programas a un canal de app

Crea un PreviewProgram.Builder y define sus atributos:

Kotlin

    val builder = PreviewProgram.Builder()
    builder.setChannelId(channelId)
            .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
            .setTitle("Title")
            .setDescription("Program description")
            .setPosterArtUri(uri)
            .setIntentUri(uri)
            .setInternalProviderId(appProgramId)
    

Java

    PreviewProgram.Builder builder = new PreviewProgram.Builder();
    builder.setChannelId(channelId)
            .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP)
            .setTitle("Title")
            .setDescription("Program description")
            .setPosterArtUri(uri)
            .setIntentUri(uri)
            .setInternalProviderId(appProgramId);
    

Agrega más atributos según el tipo de programa. (Puedes ver los atributos disponibles para cada tipo de programa en las tablas a continuación).

Inserta el programa en el proveedor:

Kotlin

    var programUri = context.contentResolver.insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
            builder.build().toContentValues())
    

Java

    Uri programUri = context.getContentResolver().insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
          builder.build().toContentValues());
    

Obtén el ID de programa para referencia futura:

Kotlin

    val programId = ContentUris.parseId(programUri)
    

Java

    long programId = ContentUris.parseId(programUri);
    

Cómo agregar programas al canal "Ver a continuación"

El proceso para insertar programas en el canal "Ver a continuación" es igual al que realizas en tu canal.

Hay cuatro tipos de programas; selecciona el apropiado:

TipoNotas
WATCH_NEXT_TYPE_CONTINUEEl usuario detuvo la reproducción mientras miraba el contenido.
WATCH_NEXT_TYPE_NEXTEstá disponible el siguiente programa de una serie que el usuario está mirando. Por ejemplo, si el usuario está mirando el episodio 3 de una serie, la app puede sugerirle que mire a continuación el episodio 4.
WATCH_NEXT_TYPE_NEWContenido nuevo disponible directamente relacionado con lo que el usuario está mirando. Por ejemplo, el usuario está mirando el episodio número 5 de una serie y el episodio 6 pasa a estar disponible.
WATCH_NEXT_TYPE_WATCHLISTLo inserta el sistema o la app cuando el usuario guarda un programa.

Usa WatchNextProgram.Builder:

Kotlin

    val builder = WatchNextProgram.Builder()
    builder.setType(TvContractCompat.WatchNextPrograms.TYPE_CLIP)
            .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
            .setLastEngagementTimeUtcMillis(time)
            .setTitle("Title")
            .setDescription("Program description")
            .setPosterArtUri(uri)
            .setIntentUri(uri)
            .setInternalProviderId(appProgramId)

    val watchNextProgramUri = context.contentResolver
            .insert(TvContractCompat.WatchNextPrograms.CONTENT_URI,
                    builder.build().toContentValues())
    

Java

    WatchNextProgram.Builder builder = new WatchNextProgram.Builder();
    builder.setType(TvContractCompat.WatchNextPrograms.TYPE_CLIP)
            .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE)
            .setLastEngagementTimeUtcMillis(time)
            .setTitle("Title")
            .setDescription("Program description")
            .setPosterArtUri(uri)
            .setIntentUri(uri)
            .setInternalProviderId(appProgramId);

    Uri watchNextProgramUri = context.getContentResolver()
            .insert(TvContractCompat.WatchNextPrograms.CONTENT_URI, builder.build().toContentValues());
    

Usa TvContractCompat.buildWatchNextProgramUri(long watchNextProgramId) a fin de crear la Uri que necesitas para actualizar un programa de "Ver a continuación".

Cuando el usuario agrega el programa a "Ver a continuación", el sistema lo copia en la fila. Envía el intent TvContractCompat.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT para notificar a la app que se agregó el programa. El intent incluye dos elementos adicionales: el ID de programa que se copió y el ID de programa que se creó para el canal "Ver a continuación".

Cómo actualizar un programa

Puedes cambiar la información de un programa, por ejemplo, si quieres actualizar el precio del alquiler de una película o actualizar la barra de progreso que muestra qué porcentaje de un programa vio el usuario.

Usa PreviewProgram.Builder para definir los atributos que necesitas cambiar y, luego llama a getContentResolver().update para actualizar el programa. Especifica el ID de programa que guardaste cuando se agregó originalmente el programa:

Kotlin

    context.contentResolver.update(
            TvContractCompat.buildPreviewProgramUri(programId),
                    builder.build().toContentValues(), null, null
    )
    

Java

    context.getContentResolver().update(TvContractCompat.buildPreviewProgramUri(programId),
        builder.build().toContentValues(), null, null);
    

Cómo borrar un programa

Kotlin

    context.contentResolver
            .delete(TvContractCompat.buildPreviewProgramUri(programId), null, null)
    

Java

    context.getContentResolver().delete(TvContractCompat.buildPreviewProgramUri(programId), null, null);
    

Cómo controlar las acciones del usuario

Tu app can puede ayudar a los usuarios a descubrir contenido mediante una IU que permita mostrar y agregar programas. La app también deberá controlar las interacciones con tus canales una vez que se muestren en la pantalla principal.

Cómo descubrir y agregar canales

Tu app puede proporcionar un elemento de IU que permita al usuario seleccionar y agregar sus canales (por ejemplo, un botón que solicite agregar el canal).

Después de que el usuario solicite un canal específico, ejecuta este código para obtener el permiso del usuario y agregarlo a la IU de la pantalla principal:

Kotlin

    val intent = Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE)
    intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId)
    try {
      activity.startActivityForResult(intent, 0)
    } catch (e: ActivityNotFoundException) {
      // handle error
    }
    

Java

    Intent intent = new Intent(TvContractCompat.ACTION_REQUEST_CHANNEL_BROWSABLE);
    intent.putExtra(TvContractCompat.EXTRA_CHANNEL_ID, channelId);
    try {
       activity.startActivityForResult(intent, 0);
    } catch (ActivityNotFoundException e) {
      // handle error
    }
    

El sistema muestra un diálogo que solicita al usuario que apruebe el canal. Controla el resultado de la solicitud en el método onActivityResult de tu actividad (Activity.RESULT_CANCELED o Activity.RESULT_OK).

Eventos de la pantalla principal de Android TV

Cuando el usuario interactúa con los programas/canales publicados por la app, la pantalla principal envía instancias de intent a la app:

  • La pantalla principal envía a la app la Uri almacenada en el atributo APP_LINK_INTENT_URI de un canal cuando el usuario selecciona el logotipo del canal. La app debe lanzar su IU principal o una vista relacionada con el canal seleccionado.
  • La pantalla principal envía la Uri almacenada en el atributo INTENT_URI de un programa a la app cuando el usuario selecciona un programa. La app debe mostrar el contenido seleccionado.
  • El usuario puede indicar que ya no está interesado en un programa y que quiere quitarlo de la IU de la pantalla principal. El sistema quita el programa de la IU y envía a la app propietaria del programa un intent (android.media.tv.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED o android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED) con el ID del programa. La app debe quitar el programa del proveedor y NO volver a insertarlo.

Asegúrate de crear filtros de intent para todas las Uris que la pantalla principal envía sobre interacciones del usuario, por ejemplo:

<receiver
       android:name=".WatchNextProgramRemoved"
       android:enabled="true"
       android:exported="true">
       <intent-filter>
           <action android:name="android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED" />
       </intent-filter>
    </receiver>
    

Recomendaciones

  • Muchas apps de TV requieren que los usuarios accedan. En este caso, el BroadcastReceiver que escucha android.media.tv.action.INITIALIZE_PROGRAMS debe sugerir contenido del canal para los usuarios no autenticados. Por ejemplo, la app inicialmente puede mostrar el mejor contenido o el contenido más popular. Una vez que el usuario accede, puede mostrar contenido personalizado. Esto es ideal para realizar ventas verticales de apps a los usuarios antes de que accedan. La app de ejemplo leanback-homescreen-channels muestra cómo cargar canales después de que se instaló la app (o después de configurar el dispositivo si la app ya está instalada).
  • Si la app no está en primer plano y necesitas actualizar un canal o un programa, usa JobScheduler para programar el trabajo (consulta: JobScheduler y JobService).
  • El sistema puede revocar los permisos del proveedor de tu app si esta no funciona de manera correcta (por ejemplo, si envía spam de datos continuamente al proveedor). Asegúrate de encapsular el código que accede al proveedor con cláusulas try-catch para controlar las excepciones de seguridad.
  • Antes de actualizar los programas y canales, pídele al proveedor los datos que necesitas para actualizar y concílialos. Por ejemplo, no es necesario actualizar un programa que el usuario quiere quitar de la IU. Usa un trabajo en segundo plano que inserte/actualice los datos en el proveedor después de consultar sobre los datos existentes y solicitar la aprobación de los canales. Puedes ejecutar este trabajo cuando se inicia la app y cuando esta necesita actualizar sus datos.

    Kotlin

        context.contentResolver
          .query(
              TvContractCompat.buildChannelUri(channelId),
                  null, null, null, null).use({
                      cursor-> if (cursor != null and cursor.moveToNext()) {
                                   val channel = Channel.fromCursor(cursor)
                                   if (channel.isBrowsable()) {
                                       //update channel's programs
                                   }
                               }
                  })
        

    Java

        try (Cursor cursor = context.getContentResolver()
              .query(
                  TvContractCompat.buildChannelUri(channelId),
                  null,
                  null,
                  null,
                  null)) {
                      if (cursor != null && cursor.moveToNext()) {
                          Channel channel = Channel.fromCursor(cursor);
                          if (channel.isBrowsable()) {
                              //update channel's programs
                          }
                      }
                  }
        
  • Use URI exclusivas para todas las imágenes (logotipos, íconos, imágenes de contenido). Asegúrate de usar una URI diferente cuando actualices una imagen. Todas las imágenes se almacenan en caché. Si no modificas URI cuando cambias la imagen, se seguirá mostrando la imagen anterior.

  • Recuerda que las cláusulas WHERE no están permitidas y las llamadas a los proveedores con cláusulas WHERE generarán una excepción de seguridad.

Atributos

En esta sección, se describen los atributos de canal y de programa por separado.

Atributos de canal

Debes especificar los siguientes atributos para cada canal:

Atributo Notas
TIPO Se define en TYPE_PREVIEW.
DISPLAY_NAME Se define en el nombre del canal.
APP_LINK_INTENT_URI Cuando el usuario selecciona el logotipo del canal, el sistema envía un intent para iniciar una actividad que presenta el contenido relevante al canal. Define este atributo en la URI usada por el filtro de intent para esa actividad.

Además, un canal también tiene seis campos reservados para uso interno de la app. Estos campos se pueden usar para almacenar claves y otros valores que pueden ayudar a la app a asignar el canal a la estructura interna de datos:

  • INTERNAL_PROVIDER_ID
  • INTERNAL_PROVIDER_DATA
  • INTERNAL_PROVIDER_FLAG1
  • INTERNAL_PROVIDER_FLAG2
  • INTERNAL_PROVIDER_FLAG3
  • INTERNAL_PROVIDER_FLAG4

Atributos de programa

Consulta las páginas individuales de los atributos para cada tipo de programa:

Código de ejemplo

Para obtener más información sobre cómo compilar apps que interactúan con la pantalla principal y agregar canales y programas a la pantalla principal de Android TV, consulta el codelab de nuestra pantalla principal y el ejemplo de GitHub.