Android N 以前でのおすすめの表示

テレビを操作する際、ユーザーは一般的に、コンテンツを視聴する前に最小限の操作しか行わないことを好む。多くの TV ユーザーにとって理想的なシナリオは、「TV の前に座り、電源を入れ、コンテンツを視聴する」というものです。ユーザーが好むコンテンツにたどり着くまでの手順が最も少ない方法が、一般的にユーザーが好む方法である。

注: ここで説明する API は、Android 7.1 (API レベル 25) までの Android バージョンで実行されるアプリでレコメンデーションを行う場合にのみ使用します。Android 8.0 (API レベル 26) 以降で動作するアプリに推奨事項を提供するには、アプリは 推奨チャネル を使用する必要があります。

Android フレームワークは、ホーム画面におすすめ項目を表示する行を設けることで、最小限の入力での操作を支援します。デバイスの初回使用後、コンテンツのおすすめがテレビのホーム画面の最初の行に表示されます。アプリのコンテンツカタログからおすすめ情報を提供することで、ユーザーをアプリに呼び戻すことができます。

Android TV のホーム画面におけるおすすめ項目の例
図 1. おすすめ項目の例。

このガイドでは、ユーザーがアプリのコンテンツを発見して楽しめるように、おすすめ機能を作成し、それを Android フレームワークに提供する方法を解説します。 Leanback サンプルアプリ のサンプル実装も参照してください。

おすすめに関するベスト プラクティス

ユーザーはおすすめを利用することで、目当てのコンテンツやアプリをすばやく見つけることができます。質の高い、ユーザーにとって関連性の高いおすすめコンテンツを作成することは、テレビアプリで優れたユーザーエクスペリエンスを実現する上で重要な要素です。そのため、ユーザーに提示するおすすめコンテンツを慎重に検討し、適切に管理する必要があります。

おすすめのタイプ

おすすめコンテンツを作成する際は、ユーザーが未完了の閲覧履歴にリンクするか、関連コンテンツへとつながるようなアクティビティを提案するべきです。以下に、検討すべき具体的な推奨事項をいくつか示します。

  • 続きコンテンツ ユーザーがシリーズの視聴を再開するための次のエピソードのおすすめ。あるいは、一時停止した映画、テレビ番組、ポッドキャストに対して続きのおすすめを表示することで、ユーザーは数回クリックするだけで一時停止したコンテンツの視聴を再開できます。
  • 新規コンテンツ: ユーザーがシリーズの視聴を終了したときに、新たに封切られた別のエピソードなどに対してこのタイプを使用します。また、ユーザーがアプリを通じてコンテンツをチャンネル登録、フォロー、追跡できるようにする場合にも、追跡対象のコンテンツのリスト内にある未視聴のアイテムに対してこのタイプを使用します。
  • 関連コンテンツ: ユーザーのこれまでの視聴行動に基づいておすすめを提示します。

ユーザー エクスペリエンスを最大限に高めるおすすめカードのデザイン方法について詳しくは、Android TV デザイン仕様のおすすめの行をご覧ください。

おすすめを更新する

おすすめを更新する場合、単におすすめを削除して再投稿しないでください。このようにすると、そのおすすめがおすすめの行の最後に表示されます。映画などのコンテンツ アイテムは、いったん再生されたらおすすめから 削除します。

おすすめをカスタマイズ

ユーザー インターフェースの要素(カードのフォアグラウンドおよびバックグラウンドの画像、色、アプリアイコン、タイトル、サブタイトルなど)を設定することにより、おすすめカードをカスタマイズしてブランド情報を伝えることができます。詳しくは、Android TV デザイン仕様のおすすめの行をご覧ください。

おすすめをグループ化する

必要に応じて、おすすめのソースに基づいておすすめをグループ化できます。たとえば、おすすめの 2 つのグループ(ユーザーがチャンネル登録しているコンテンツのおすすめ、ユーザーが知らない可能性がある新しい注目コンテンツのおすすめ)をアプリで提供することもできます。

おすすめの行を作成または更新するとき、システムによっておすすめのランク付けと順位付けがグループごとに行われます。おすすめの順位が無関係のおすすめより下位にならないようにするには、おすすめのグループ情報を提供します。

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 オブジェクトとして推奨事項を受け取ります。

値を設定する

推奨カードの UI 要素の値を設定するには、以下に説明するビルダーパターンに従うビルダークラスを作成します。まず、レコメンデーションカードの要素の値を設定します。

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

通知の作成

値を設定したら、次に通知を作成し、ビルダー クラスから通知に値を割り当て、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 クラスを拡張して、30 分ごとにレコメンデーションサービスを定期的に実行するようにします。

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