Рекомендации в Android 7.0 и более ранних версиях

При взаимодействии с телевизорами пользователи обычно предпочитают вводить минимум данных перед просмотром контента. Идеальный сценарий для многих пользователей телевизоров: сесть, включить и смотреть. Чем меньше шагов нужно сделать пользователю, чтобы добраться до нужного контента, тем лучше.

Примечание. Используйте описанные здесь API для создания рекомендаций только в приложениях, работающих на Android 7.1 (уровень API 25) и более ранних версиях. Чтобы показывать рекомендации для приложений, работающих на Android 8.0 (уровень API 26) и более поздних версий, ваше приложение должно использовать каналы рекомендаций.

Фреймворк Android помогает минимизировать взаимодействие с пользователем, предоставляя строку рекомендаций на главном экране. Рекомендации контента появляются в первой строке главного экрана телевизора после первого использования устройства. Рекомендации из каталога контента вашего приложения могут помочь вернуть пользователей в ваше приложение.

Пример строки рекомендаций на главном экране Android TV
Рисунок 1. Пример строки рекомендаций.

В этом руководстве рассказывается, как создавать рекомендации и передавать их платформе Android, чтобы пользователи могли находить и смотреть контент из вашего приложения. Пример реализации можно найти в приложении Leanback.

Рекомендации по работе с рекомендациями

Рекомендации помогают пользователям быстро находить нужный контент и приложения. Чтобы пользователям было удобно работать с вашим ТВ-приложением, важно предлагать им качественные и релевантные рекомендации. Поэтому тщательно выбирайте и контролируйте, что вы рекомендуете.

Типы рекомендаций

При создании рекомендаций вы должны возвращать пользователей к незавершенным действиям по просмотру или предлагать действия, которые расширяют это до связанного контента. Вот несколько типов рекомендаций, которые стоит учитывать:

  • Контент для продолжения просмотра – рекомендации следующей серии, чтобы пользователи могли продолжить просмотр сериала. Или используйте рекомендации по продолжению просмотра фильмов, телешоу или подкастов, чтобы пользователи могли вернуться к просмотру приостановленного контента всего за несколько кликов.
  • Новый контент, например первая серия нового сериала, если пользователь закончил смотреть другой сериал. Если в вашем приложении можно подписываться на контент или отслеживать его, рекомендуйте пользователям новые материалы из их списка.
  • Рекомендации по контенту на основе истории просмотров.

Подробную информацию о том, как создавать карточки рекомендаций, чтобы они были удобны для пользователей, можно найти в разделе Строка рекомендаций спецификации дизайна Android TV.

Обновить фид

При обновлении рекомендаций не удаляйте и не публикуйте их заново, так как в этом случае они будут появляться в конце строки рекомендаций. После воспроизведения контента, например фильма, удалите его из рекомендаций.

Настроить рекомендации

Вы можете настроить карточки рекомендаций так, чтобы они передавали информацию о бренде, задав элементы пользовательского интерфейса, такие как изображение переднего плана и фона карточки, цвет, значок приложения, заголовок и подзаголовок. Подробную информацию можно найти в разделе Строка рекомендаций спецификации дизайна Android TV.

Рекомендации групп

При желании вы можете сгруппировать рекомендации по источнику. Например, в приложении может быть две группы рекомендаций: рекомендации контента, на который пользователь подписан, и рекомендации нового популярного контента, о котором пользователь может не знать.

При создании или обновлении строки рекомендаций система ранжирует и упорядочивает рекомендации для каждой группы отдельно. Указав информацию о группе для рекомендаций, вы можете быть уверены, что они не будут упорядочены под несвязанными рекомендациями.

Используйте NotificationCompat.Builder.setGroup(), чтобы задать строку ключа группы для рекомендации. Например, чтобы пометить рекомендацию как принадлежащую к группе, содержащей новый популярный контент, можно вызвать setGroup("trending").

Как создать сервис рекомендаций

Рекомендации по контенту создаются в фоновом режиме. Чтобы ваше приложение могло добавлять рекомендации, создайте сервис, который будет периодически добавлять контент из каталога вашего приложения в список рекомендаций системы.

В следующем примере кода показано, как расширить IntentService, чтобы создать сервис рекомендаций для приложения:

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

Чтобы система распознала и запустила этот сервис, зарегистрируйте его в манифесте приложения. В следующем фрагменте кода показано, как объявить этот класс в качестве сервиса:

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

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

Создание рекомендаций

После запуска сервис должен создавать рекомендации и передавать их в платформу Android. Фреймворк получает рекомендации в виде объектов Notification, которые используют определенный шаблон и помечены определенной категорией.

Как задать значения

Чтобы задать значения элементов интерфейса для карточки рекомендации, создайте класс builder, который соответствует шаблону builder, описанному ниже. Сначала вы задаете значения элементов карточки рекомендаций.

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

Создайте уведомление

Задав значения, вы создаете уведомление, присваивая ему значения из класса builder и вызывая NotificationCompat.Builder.build().

Также вызовите метод setLocalOnly(), чтобы уведомление NotificationCompat.BigPictureStyle не появлялось на других устройствах.

В примере кода ниже показано, как создать рекомендацию.

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

Сервис рекомендаций для бега

Сервис рекомендаций вашего приложения должен периодически запускаться, чтобы создавать актуальные рекомендации. Чтобы запустить сервис, создайте класс, который запускает таймер и вызывает его через регулярные интервалы. В следующем примере кода класс BroadcastReceiver расширен, чтобы запускать периодическое выполнение сервиса рекомендаций каждые полчаса:

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

Эта реализация класса BroadcastReceiver должна запускаться после включения телевизора, на котором она установлена. Для этого зарегистрируйте класс в манифесте приложения с фильтром интентов, который отслеживает завершение процесса загрузки устройства. В приведенном ниже образце кода показано, как добавить эту конфигурацию в манифест.

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

Важно! Чтобы получать уведомления о завершении загрузки, приложение должно запрашивать разрешение RECEIVE_BOOT_COMPLETED. Дополнительную информацию можно найти по адресу ACTION_BOOT_COMPLETED.

В методе onHandleIntent() класса сервиса рекомендаций опубликуйте рекомендацию в менеджере следующим образом:

Kotlin

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

Java

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