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

Recomendaciones para Android N y versiones anteriores

Cuando los usuarios interactúan con una TV, por lo general, prefieren interactuar lo menos posible antes de mirar contenido. Una situación ideal para muchos usuarios consiste en sentarse, encender la TV y mirar el contenido. Por lo general, el proceso ideal es el que menos pasos requiere para que los usuarios puedan acceder al contenido que les interesa.

Nota: Solo usa las API que se describen aquí para hacer recomendaciones en las apps con Android 7.1 y versiones posteriores (API nivel 25). Si deseas brindar recomendaciones para las apps que ejecutan Android 8.0 (API nivel 26) y versiones posteriores, tu app debe usar los canales de recomendación.

El marco de trabajo de Android asiste con un nivel mínimo de interacción gracias a la fila de recomendaciones que ofrece en la pantalla principal. Las recomendaciones de contenido aparecen en la primera fila de la pantalla principal de la TV, después de que se usa el dispositivo por primera vez. Brindar recomendaciones del catálogo de contenido de tu app puede ayudar a que los usuarios vuelvan a usarla.

Figura 1: Ejemplo de la fila de recomendaciones.

En esta lección, aprenderás cómo crear recomendaciones y mostrarlas en el marco de trabajo de Android a fin de que los usuarios puedan descubrir y disfrutar fácilmente el contenido de tu app. En este debate, se describe parte del código de la app de muestra de Android Leanback en el repositorio de GitHub de Android TV.

Prácticas recomendadas para las recomendaciones

Las recomendaciones permiten que los usuarios encuentren rápidamente el contenido y las apps que les gustan. Brindar recomendaciones que sean de alta calidad y relevantes para los usuarios es un factor importante a la hora de crear una gran experiencia del usuario con tu app para TV. Por este motivo, debes decidir cuidadosamente las recomendaciones que les haces a los usuarios y administrarlas de cerca.

Tipos de recomendaciones

Cuando creas recomendaciones, debes vincular a los usuarios con actividades de visualización incompletas o sugerir actividades que dirijan a contenido relacionado. Los siguientes son tipos de recomendaciones específicos que deberías tener en cuenta:

  • Recomendaciones de contenido de continuación para el episodio siguiente a fin de que los usuarios continúen mirando una serie. También puedes usar recomendaciones de continuación para películas, programas de TV o podcasts pausados de manera que los usuarios puedan volver a ver contenido con unos pocos clics.
  • Recomendaciones de contenido nuevo, como para un episodio que todavía no se reprodujo, si el usuario terminó de ver otra serie. Además, si tu app les permite a los usuarios suscribirse a contenido, seguirlo o realizarle un seguimiento, usa recomendaciones de contenido nuevo para elementos sin visualizar en su lista de contenido con seguimiento.
  • Recomendaciones de contenido relacionado basadas en el comportamiento de visualización histórico de los usuarios.

Para obtener más información sobre cómo diseñar tarjetas de recomendaciones para la mejor experiencia del usuario, consulta la fila de recomendaciones en las especificaciones de diseño de Android TV.

Actualiza recomendaciones

Cuando actualices recomendaciones, no las quites y vuelvas a publicarlas, ya que esto provoca que aparezcan al final de la fila de recomendaciones. Una vez que se haya reproducido un elemento, como una película, quítalo de las recomendaciones.

Personaliza recomendaciones

Puedes personalizar tarjetas de recomendaciones para que transmitan información sobre una marca. Para ello, configura los elementos de la interfaz de usuario, como la imagen en primer plano y de fondo de la tarjeta, el color, el ícono de la app, el título y el subtítulo. Si deseas obtener más información, consulta la fila de recomendaciones en las especificaciones de diseño de Android TV.

Recomendaciones de grupo

También puedes agrupar las recomendaciones según su origen. Por ejemplo, tu app podría brindar dos grupos de recomendaciones: contenido al que el usuario está suscrito y contenido popular nuevo que el usuario podría desconocer.

El sistema clasifica y ordena las recomendaciones para cada grupo por separado cuando creas o actualizas la fila de recomendaciones. Si proporcionas información sobre el grupo de tus recomendaciones, no se ordenarán debajo de recomendaciones no relacionadas.

Usa NotificationCompat.Builder.setGroup() para definir la string de clave del grupo de una recomendación. Por ejemplo, para marcar que una recomendación pertenece a un grupo que incluye contenido de actualidad nuevo, puedes llamar a setGroup("trending").

Crea un servicio de recomendaciones

Las recomendaciones de contenido se crean con procesos en segundo plano. Si deseas que tu aplicación contribuya con recomendaciones, crea un servicio que agregue de forma periódica anuncios del catálogo de tu app a la lista de recomendaciones del sistema.

En el siguiente código de ejemplo, se muestra cómo extender IntentService a fin de crear un servicio de recomendación para tu aplicación:

Kotlin

    class UpdateRecommendationsService : IntentService("RecommendationService") {
        override protected fun onHandleIntent(intent: Intent) {
            Log.d(TAG, "Updating recommendation cards")
            val recommendations = VideoProvider.getMovieList()
            if (recommendations == null) return

            var count = 0

            try {
                val builder = RecommendationBuilder()
                        .setContext(applicationContext)
                        .setSmallIcon(R.drawable.videos_by_google_icon)

                for (entry in recommendations.entrySet()) {
                    for (movie in entry.getValue()) {
                        Log.d(TAG, "Recommendation - " + movie.getTitle())

                        builder.setBackground(movie.getCardImageUrl())
                                .setId(count + 1)
                                .setPriority(MAX_RECOMMENDATIONS - count)
                                .setTitle(movie.getTitle())
                                .setDescription(getString(R.string.popular_header))
                                .setImage(movie.getCardImageUrl())
                                .setIntent(buildPendingIntent(movie))
                                .build()
                        if (++count >= MAX_RECOMMENDATIONS) {
                            break
                        }
                    }
                    if (++count >= MAX_RECOMMENDATIONS) {
                        break
                    }
                }
            } catch (e: IOException) {
                Log.e(TAG, "Unable to update recommendation", e)
            }
        }

        private fun buildPendingIntent(movie: Movie): PendingIntent {
            val detailsIntent = Intent(this, DetailsActivity::class.java)
            detailsIntent.putExtra("Movie", movie)

            val stackBuilder = TaskStackBuilder.create(this)
            stackBuilder.addParentStack(DetailsActivity::class.java)
            stackBuilder.addNextIntent(detailsIntent)

            // Ensure a unique PendingIntents, otherwise all
            // recommendations end up with the same PendingIntent
            detailsIntent.setAction(movie.getId().toString())

            val intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
            return intent
        }

        companion object {
            private val TAG = "UpdateRecommendationsService"
            private val MAX_RECOMMENDATIONS = 3
        }
    }
    

Java

    public class UpdateRecommendationsService extends IntentService {
        private static final String TAG = "UpdateRecommendationsService";
        private static final int MAX_RECOMMENDATIONS = 3;

        public UpdateRecommendationsService() {
            super("RecommendationService");
        }

        @Override
        protected void onHandleIntent(Intent intent) {
            Log.d(TAG, "Updating recommendation cards");
            HashMap<String, List<Movie>> recommendations = VideoProvider.getMovieList();
            if (recommendations == null) return;

            int count = 0;

            try {
                RecommendationBuilder builder = new RecommendationBuilder()
                        .setContext(getApplicationContext())
                        .setSmallIcon(R.drawable.videos_by_google_icon);

                for (Map.Entry<String, List<Movie>> entry : recommendations.entrySet()) {
                    for (Movie movie : entry.getValue()) {
                        Log.d(TAG, "Recommendation - " + movie.getTitle());

                        builder.setBackground(movie.getCardImageUrl())
                                .setId(count + 1)
                                .setPriority(MAX_RECOMMENDATIONS - count)
                                .setTitle(movie.getTitle())
                                .setDescription(getString(R.string.popular_header))
                                .setImage(movie.getCardImageUrl())
                                .setIntent(buildPendingIntent(movie))
                                .build();

                        if (++count >= MAX_RECOMMENDATIONS) {
                            break;
                        }
                    }
                    if (++count >= MAX_RECOMMENDATIONS) {
                        break;
                    }
                }
            } catch (IOException e) {
                Log.e(TAG, "Unable to update recommendation", e);
            }
        }

        private PendingIntent buildPendingIntent(Movie movie) {
            Intent detailsIntent = new Intent(this, DetailsActivity.class);
            detailsIntent.putExtra("Movie", movie);

            TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
            stackBuilder.addParentStack(DetailsActivity.class);
            stackBuilder.addNextIntent(detailsIntent);
            // Ensure a unique PendingIntents, otherwise all
            // recommendations end up with the same PendingIntent
            detailsIntent.setAction(Long.toString(movie.getId()));

            PendingIntent intent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
            return intent;
        }
    }
    

Para que el sistema reconozca este servicio y lo ejecute, regístralo mediante el manifiesto de tu app. En el siguiente fragmento de código, se muestra cómo declarar esta clase como un servicio:

    <manifest ... >
      <application ... >
        ...

        <service
                android:name="com.example.android.tvleanback.UpdateRecommendationsService"
                android:enabled="true" />
      </application>
    </manifest>
    

Compila recomendaciones

Una vez que tu servicio de recomendaciones comience a funcionar, deberá crear recomendaciones y pasarlas al marco de trabajo de Android, que las recibirá como objetos de Notification que usan una plantilla específica y están marcados con una categoría determinada.

Cómo configurar los valores

Si deseas definir los valores de los elementos de IU correspondientes a la tarjeta de recomendaciones, crea una clase de compilador que siga el patrón de compilador que se describe a continuación. En primer lugar, define los valores de los elementos de la tarjeta de recomendaciones.

Kotlin

    class RecommendationBuilder {
        ...

        fun setTitle(title: String): RecommendationBuilder {
            this.title = title
            return this
        }

        fun setDescription(description: String): RecommendationBuilder {
            this.description = description
            return this
        }

        fun setImage(uri: String): RecommendationBuilder {
            imageUri = uri
            return this
        }

        fun setBackground(uri: String): RecommendationBuilder {
            backgroundUri = uri
            return this
        }

    ...
    

Java

    public class RecommendationBuilder {
        ...

        public RecommendationBuilder setTitle(String title) {
                this.title = title;
                return this;
            }

            public RecommendationBuilder setDescription(String description) {
                this.description = description;
                return this;
            }

            public RecommendationBuilder setImage(String uri) {
                imageUri = uri;
                return this;
            }

            public RecommendationBuilder setBackground(String uri) {
                backgroundUri = uri;
                return this;
            }
    ...
    

Crea la notificación

Una vez que hayas definido los valores, compila la notificación, asigna los valores de la clase de compilador y llama a NotificationCompat.Builder.build().

Además, asegúrate de llamar a setLocalOnly() para que no se muestre la notificación NotificationCompat.BigPictureStyle en otros dispositivos.

En el siguiente código, se muestra cómo compilar una recomendación.

Kotlin

    class RecommendationBuilder {
        ...

        @Throws(IOException::class)
        fun build(): Notification {
            ...

            val notification = NotificationCompat.BigPictureStyle(
            NotificationCompat.Builder(context)
                    .setContentTitle(title)
                    .setContentText(description)
                    .setPriority(priority)
                    .setLocalOnly(true)
                    .setOngoing(true)
                    .setColor(context.resources.getColor(R.color.fastlane_background))
                    .setCategory(Notification.CATEGORY_RECOMMENDATION)
                    .setLargeIcon(image)
                    .setSmallIcon(smallIcon)
                    .setContentIntent(intent)
                    .setExtras(extras))
                    .build()

            return notification
        }
    }
    

Java

    public class RecommendationBuilder {
        ...

        public Notification build() throws IOException {
            ...

            Notification notification = new NotificationCompat.BigPictureStyle(
                    new NotificationCompat.Builder(context)
                            .setContentTitle(title)
                            .setContentText(description)
                            .setPriority(priority)
                            .setLocalOnly(true)
                            .setOngoing(true)
                            .setColor(context.getResources().getColor(R.color.fastlane_background))
                            .setCategory(Notification.CATEGORY_RECOMMENDATION)
                            .setLargeIcon(image)
                            .setSmallIcon(smallIcon)
                            .setContentIntent(intent)
                            .setExtras(extras))
                    .build();

            return notification;
        }
    }
    

Ejecuta el servicio de recomendaciones

El servicio de recomendaciones de tu app se debe ejecutar de forma periódica a fin de crear recomendaciones actuales. Para poner en funcionamiento tu servicio, crea una clase que ejecute un temporizador y lo invoque en intervalos regulares. En el siguiente código de ejemplo, se extiende la clase BroadcastReceiver a fin de comenzar la ejecución periódica de un servicio de recomendaciones cada media hora:

Kotlin

    class BootupActivity : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            Log.d(TAG, "BootupActivity initiated")
            if (intent.action.endsWith(Intent.ACTION_BOOT_COMPLETED)) {
                scheduleRecommendationUpdate(context)
            }
        }

        private fun scheduleRecommendationUpdate(context: Context) {
            Log.d(TAG, "Scheduling recommendations update")
            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            val recommendationIntent = Intent(context, UpdateRecommendationsService::class.java)
            val alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0)
            alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    INITIAL_DELAY,
                    AlarmManager.INTERVAL_HALF_HOUR,
                    alarmIntent
            )
        }

        companion object {
            private val TAG = "BootupActivity"
            private val INITIAL_DELAY:Long = 5000
        }
    }
    

Java

    public class BootupActivity extends BroadcastReceiver {
        private static final String TAG = "BootupActivity";

        private static final long INITIAL_DELAY = 5000;

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "BootupActivity initiated");
            if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
                scheduleRecommendationUpdate(context);
            }
        }

        private void scheduleRecommendationUpdate(Context context) {
            Log.d(TAG, "Scheduling recommendations update");

            AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
            Intent recommendationIntent = new Intent(context, UpdateRecommendationsService.class);
            PendingIntent alarmIntent = PendingIntent.getService(context, 0, recommendationIntent, 0);

            alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                    INITIAL_DELAY,
                    AlarmManager.INTERVAL_HALF_HOUR,
                    alarmIntent);
        }
    }
    

La implementación de la clase BroadcastReceiver se debe ejecutar después de haber iniciado la TV en el lugar en el que está instalada. Para lograrlo, registra esta clase en el manifiesto de tu app con un filtro de intent que escuche cuando termine el proceso de inicialización del dispositivo. En el siguiente código de ejemplo, se muestra cómo agregar esta configuración al manifiesto:

    <manifest ... >
      <application ... >
        <receiver android:name="com.example.android.tvleanback.BootupActivity"
                  android:enabled="true"
                  android:exported="false">
          <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED"/>
          </intent-filter>
        </receiver>
      </application>
    </manifest>
    

Importante: Para recibir una notificación de que se completó el proceso de inicialización, tu app debe solicitar el permiso RECEIVE_BOOT_COMPLETED. Si deseas obtener más información, consulta ACTION_BOOT_COMPLETED.

En el método onHandleIntent() de la clase de servicio de tu recomendación, publica la recomendación al administrador de la siguiente manera:

Kotlin

    val notification = notificationBuilder.build()
    notificationManager.notify(id, notification)
    

Java

    Notification notification = notificationBuilder.build();
    notificationManager.notify(id, notification);