Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c27d5fd6 authored by Joe Fernandez's avatar Joe Fernandez Committed by Android Git Automerger
Browse files

am 7cec35ef: Merge "docs: b/18122848 [DAC] Android TV - Recommendation card" into lmp-docs

* commit '7cec35ef':
  docs: b/18122848 [DAC] Android TV - Recommendation card
parents 66fefd0c 7cec35ef
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ see <a href="/distribute/googleplay/tv.html">Distributing to Android TV</a>.</p>
<p>To learn more about searching within your app, see
  <a href="{@docRoot}training/tv/discovery/in-app-search.html">Searching within TV Apps</a>.

<h2>Recommendations</h2>
<h2 id="recommendations">Recommendations</h2>

<p>The recommendations row on Android TV is a central feature of the Home Screen that allows
  users quick access to dynamic and relevant content for their media-consumption activities. The
+78 −34
Original line number Diff line number Diff line
@@ -15,24 +15,16 @@ page.title=UI Patterns for TV

<img src="{@docRoot}design/tv/images/focus.png" alt="TV navigation and focus diagram" />

<p>A key aspect of making your application work well with a D-Pad controller is to make sure
<p>A key aspect of making your application work well with a D-pad controller is to make sure
  that there is always an object that is obviously in focus. Your app must clearly indicate
  what object is focused, so users can easily see what action they can take. Use scale, shadow
  brightness, opacity, animation or a combination of these attributes to help users see a focused
  object.</p>

<h2 id="banner">App and Game Banners</h3>

<h2>Icons</h2>

<p>Apps on TV devices require some additional icon images for presentation in the system
  user interface, including home screen launcher images (banners) and recommendation icons.
  The visual specifications for these icons are shown below.</p>


<h3 id="banner">Banners</h3>

<p>App Banners represent your app on the home screen of TV devices and serve and as a way for
  users to launch your app. Here are specific requirements for a banner image:
<p>App Banners represent your app or game on the home screens of TV devices and serve and as a way for
  users to launch your app. Here are the specific requirements for a banner image:
</p>

<ul>
@@ -44,45 +36,97 @@ page.title=UI Patterns for TV
<p>See <a href="{@docRoot}training/tv/start/start.html#banner">Provide a home screen banner</a>
in Get Started with TV Apps for more information.</p>

<h3>Recommendation Icons</h3>
<h2 id="recommendation">Recommendations</h2>

<p>Recommendation cards include a small icon that is imposed over a colored background.
  An example and specifications for this icon are shown below:</p>
<p>The first row of the Android TV home screen displays cards for content recommended by applications.
Your application provides these recommendations, as described in <a href="{@docRoot}training/tv/discovery/recommendations.html">
</a>. For a visual overview of recommendations, see <a href="design/tv/index.html#recommendations">
Design for Android TV</a>.</p>

<img src="{@docRoot}design/tv/images/icon.png" alt="Recommendation icon examples" />
<div class="layout-content-row">
  <div class="layout-content-col span-8 with-callouts">

  <p>The design elements of the recommendation card are as follows:</p>
  <ol>
  <li><strong>Large icon</strong></li>
  <li><strong>Content title</strong></li>
  <li><strong>Content text</strong></li>
  <li><strong>Small icon</strong></li>
  </ol>

  <p>The design specifications for these elements are described below.</p>

  <p>You can also set a background image (not shown) and the color of the card's text area in the
  recommendation notification. See <a href="{@docRoot}training/tv/discovery/recommendations.html">
  Recommendations</a> for more information.</p>

<p>Here are the requirements for recommendation icons:</p>
  </div>
  <div class="layout-content-col span-5">

    <img src="{@docRoot}images/tv/recommend-card.png">

  </div>
</div>

<h3>Background Image</h3>

<p>The background image also appears behind the recommendations
row and fills the Android TV home screen when the user selects the recommendation card. This image
should be different than the one provided for the large icon, and meet the following specifications:</p>

<ul>
  <li>Monocolor: size 16x16dp, white (#fff) icon with transparent background, PNG format</li>
  <li>Graphics should be centered within the icon image</li>
  <li>Measure 2016 x 1134 pixels (1920 x 1080 plus 5% margin for for motion)</li>
  <li id="solid-background">Must not be transparent</li>
</ul>

<p class="note">
  <strong>Note:</strong> Your app icon image may be desaturated and blended for some card
  displays.
  <strong>Note:</strong> If the background image does not meet the size requirements, the system
  scales it to fit.
</p>


<h2>Background Images</h2>

<p>Background images are displayed in the background of your app to provide additional visual
  interest, information, or branding. The user interface widgets provided in the <a href="{@docRoot}tools/support-library/features.html#v17-leanback">v17 leanback support
<p>The user interface widgets provided in the
  <a href="{@docRoot}tools/support-library/features.html#v17-leanback">v17 leanback support
  library</a> provide specific support for background images and for updating them as items gain
  and lose focus. The specific requirements for background images on TV devices is that they
  should be full color and a size of 1920 x 1080 pixels.
  and lose focus.
</p>

<p class="note" id="solid-background">
  <strong>Important:</strong> Background images must not be transparent. Your must not allow any
  portion of another app to be seen through your app.
</p>
<h3 id="icons">Icons</h3>

<h4>Large icon</h4>

<p>Typically, the large icon is an image of the content for the recommendation. It appears
above a colored area that contains the recommendation content title and text. This image should be
different from that which you provide for the background image, and conform to the following
specifications:</p>

<ul>
  <li>Height: 176dp or more</li>
  <li>Minimum width: 2/3 of the height (117dp for an image 176dp in height)</li>
  <li>Max width: 4/3 of the height (234dp for an image 176dp in height)</li>
  <li>Must not be transparent</li>
</ul>

<p class="note">
  <strong>Note:</strong> If you background image does not meet the size requirements, it is scaled
  to fit.
  <strong>Note:</strong> If the large icon does not meet the size requirements, the system
  scales it to fit.
</p>

<h4>Small icon</h4>

<p>Recommendation cards include a small icon that is imposed over a colored background. The icon and
background color display at 100% opacity when the card is selected, and at 50% opacity when not
selected.</p>

<img src="{@docRoot}design/tv/images/icon.png" alt="Recommendation icon examples" />

<p>Here are the requirements for recommendation small icons:</p>

<ul>
  <li>Flat image</li>
  <li>Monocolor: size 16x16dp, white (#fff) icon with transparent background, PNG format</li>
  <li>Graphics should be centered within the icon image</li>
</ul>

<h2>Audio Feedback</h2>

<p>Sounds on Android TV bring a cinematic quality to the interaction experience. You should
+103 KiB
Loading image diff...
+162 −71
Original line number Diff line number Diff line
@@ -14,6 +14,11 @@ trainingnavtop=true
    <li><a href="#build">Build Recommendations</a></li>
    <li><a href="#run-service">Run Recommendations Service</a></li>
  </ol>
  <h2>Try it out</h2>
  <ul>
    <li><a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android
      Leanback sample app</a></li>
  </ul>
</div>
</div>

@@ -25,7 +30,7 @@ trainingnavtop=true

<p>
  The Android framework assists with minimum-input interaction by providing a recommendations row
  on the home screen. Content recommendations appear as the first row of the TV launch screen after
  on the home screen. Content recommendations appear as the first row of the TV home screen after
  the first use of the device. Contributing recommendations from your app's content catalog can help
  bring users back to your app.
</p>
@@ -37,7 +42,9 @@ trainingnavtop=true

<p>
  This lesson teaches you how to create recommendations and provide them to the Android framework
  so your app content can be easily discovered and enjoyed by users.
  so users can easily discover and enjoy your app content. This discussion describes some code from
  the <a class="external-link" href="https://github.com/googlesamples/androidtv-Leanback">Android
  Leanback sample app</a>.
</p>


@@ -46,7 +53,7 @@ trainingnavtop=true
<p>
  Content recommendations are created with background processing. In order for your application to
  contribute to recommendations, create a service that periodically adds listings from your
  app's catalog to the system list of recommendations.
  app's catalog to the system's list of recommendations.
</p>

<p>
@@ -54,34 +61,73 @@ trainingnavtop=true
  create a recommendation service for your application:
</p>


<p class="code-caption">
  <a href="https://github.com/googlesamples/androidtv-Leanback/blob/master/app/src/main/java/com/example/android/tvleanback/UpdateRecommendationsService.java" target="_blank">
  UpdateRecommendationsService.java</a>
</p>
<pre>
public class RecommendationsService extends IntentService {
public class UpdateRecommendationsService extends IntentService {
    private static final String TAG = "UpdateRecommendationsService";
    private static final int MAX_RECOMMENDATIONS = 3;

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

    &#64;Override
    protected void onHandleIntent(Intent intent) {
        MovieDatabase database = MovieDatabase.instance(getApplicationContext());
        List<Movie> recommendations = database.recommendations();
        Log.d(TAG, "Updating recommendation cards");
        HashMap&lt;String, List&lt;Movie&gt;&gt; recommendations = VideoProvider.getMovieList();
        if (recommendations == null) return;

        int count = 0;

        try {
            for (Movie movie : recommendations) {
                // build the individual content recommendations
                buildRecommendation(getApplicationContext(), movie);
            RecommendationBuilder builder = new RecommendationBuilder()
                    .setContext(getApplicationContext())
                    .setSmallIcon(R.drawable.videos_by_google_icon);

            for (Map.Entry&lt;String, List&lt;Movie&gt;&gt; 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;
    }
}
</pre>

@@ -90,125 +136,165 @@ public class RecommendationsService extends IntentService {
  app manifest. The following code snippet illustrates how to declare this class as a service:
</p>

<p class="code-caption">
  <a href="https://github.com/googlesamples/androidtv-Leanback/blob/master/app/src/main/AndroidManifest.xml" target="_blank">
  AndroidManifest.xml</a>
</p>
<pre>
&lt;manifest ... &gt;
  &lt;application ... &gt;
    ...

    &lt;service android:name=&quot;.RecommendationsService&quot;
             android:enabled=&quot;true&quot; android:exported=&quot;true&quot;/&gt;
    &lt;service
            android:name="com.example.android.tvleanback.UpdateRecommendationsService"
            android:enabled="true" /&gt;
  &lt;/application&gt;
&lt;/manifest&gt;
</pre>

<h3 id="refreshing">Refreshing Recommendations</h3>

<p>Base your recommendations on user behavior and data such as play lists, wish lists, and associated
content. When refreshing recommendations, don't just remove and repost them, because doing so causes
the recommendations to appear at the end of the recommendations row. Once a content item, such as a
movie, has been played, <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#Removing">
remove it</a> from the recommendations.</p>

<h2 id="build">Build Recommendations</h2>

<p>
  Once your recommendation server starts running, it must create recommendations and pass them to
  Once your recommendation service starts running, it must create recommendations and pass them to
  the Android framework. The framework receives the recommendations as {@link
  android.app.Notification} objects that use a specific template and are marked with a specific
  category.
</p>

<p>
  The following code example demonstrates how to get an instance of the {@link
  android.app.NotificationManager}, build a recommendation, and post it to the manager:
</p>
<h3 id="setting-ui">Setting the Values</h3>

<pre>
public class RecommendationsService extends IntentService {
<p>To set the UI element values for the recommendation card, you create a builder class that follows
the builder pattern described as follows. First, you set the values of the recommendation card
elements.</p>

<p class="code-caption">
  <a href="https://github.com/googlesamples/androidtv-Leanback/blob/master/app/src/main/java/com/example/android/tvleanback/RecommendationBuilder.java" target="_blank">
  RecommendationBuilder.java</a>
</p>
<pre>
public class RecommendationBuilder {
    ...

    public Notification buildRecommendation(Context context, Movie movie)
            throws IOException {
    public RecommendationBuilder setTitle(String title) {
            mTitle = title;
            return this;
        }

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

        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager)
                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        public RecommendationBuilder setImage(String uri) {
            mImageUri = uri;
            return this;
        }

        Bundle extras = new Bundle();
        if (mBackgroundUri != movie.getBackgroundUri()) {
            extras.putString(EXTRA_BACKGROUND_IMAGE_URL, movie.getBackgroundUri());
        public RecommendationBuilder setBackground(String uri) {
            mBackgroundUri = uri;
            return this;
        }
...
</pre>

<h3 id="create-notification">Creating the Notification</h3>

<p>
  Once you've set the values, you then build the notification, assigning the values from the builder
  class to the notification, and calling {@link android.support.v4.app.NotificationCompat.Builder#build()
  NotificationCompat.Builder.build()}.
</p>

<p>
  Also, be sure to call
  {@link android.support.v4.app.NotificationCompat.Builder#setLocalOnly(boolean) setLocalOnly()}
  so the {@link android.support.v4.app.NotificationCompat.BigPictureStyle} notification won't show up
  on other devices.
</p>

<p>
  The following code example demonstrates how to build a recommendation, and post it to the manager.
</p>

<p class="code-caption">
  <a href="https://github.com/googlesamples/androidtv-Leanback/blob/master/app/src/main/java/com/example/android/tvleanback/RecommendationBuilder.java" target="_blank">
  RecommendationBuilder.java</a>
</p>
<pre>
public class RecommendationBuilder {
    ...

    public Notification build() throws IOException {
        ...

        // build the recommendation as a Notification object
        Notification notification = new NotificationCompat.BigPictureStyle(
                new NotificationCompat.Builder(context)
                        .setContentTitle(movie.getTitle())
                        .setContentText(movie.getDescription())
                        .setContentInfo(APP_NAME)
                        .setGroup("ActionMovies")
                        .setSortKey("0.8")
                        .setPriority(movie.getPriority())
                        .setColor(#FFFF2020)
                        .setCategory("recommendation")
                        .setLargeIcon(movie.getImage())
                        .setSmallIcon(movie.getSmallIcon())
                        .setContentIntent(buildPendingIntent(movie.getId()))
                new NotificationCompat.Builder(mContext)
                        .setContentTitle(mTitle)
                        .setContentText(mDescription)
                        .setPriority(mPriority)
                        .setLocalOnly(true)
                        .setOngoing(true)
                        .setColor(mContext.getResources().getColor(R.color.fastlane_background))
                        .setCategory(Notification.CATEGORY_RECOMMENDATION)
                        .setLargeIcon(image)
                        .setSmallIcon(mSmallIcon)
                        .setContentIntent(mIntent)
                        .setExtras(extras))
                .build();

        // post the recommendation to the NotificationManager
        mNotificationManager.notify(movie.getId(), notification);
        mNotificationManager.notify(mId, notification);
        mNotificationManager = null;
        return notification;
    }

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

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(DetailsActivity.class);
        stackBuilder.addNextIntent(detailsIntent);
        // Ensure each PendingIntent is unique
        detailsIntent.setAction(Long.toString(id));

        PendingIntent intent = stackBuilder.getPendingIntent(
                0, PendingIntent.FLAG_UPDATE_CURRENT);
        return intent;
    }
}
</pre>


<h3 id="run-service">Run Recommendations Service</h3>
<h2 id="run-service">Run Recommendations Service</h3>

<p>
  Your app's recommendation service must run periodically in order to create current
  recommendations. To run your service, create a class that runs a timer and invokes
  it at regular intervals. The following code example extends the {@link
  android.content.BroadcastReceiver} class to start periodic execution of a recommendation service
  every 12 hours:
  every half hour:
</p>

<p class="code-caption">
  <a href="https://github.com/googlesamples/androidtv-Leanback/blob/master/app/src/main/java/com/example/android/tvleanback/BootupActivity.java" target="_blank">
  BootupActivity.java</a>
</p>
<pre>
public class BootupReceiver extends BroadcastReceiver {
public class BootupActivity extends BroadcastReceiver {
    private static final String TAG = "BootupActivity";

    private static final long INITIAL_DELAY = 5000;

    &#64;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) {
        AlarmManager alarmManager = (AlarmManager)context.getSystemService(
                Context.ALARM_SERVICE);
        Intent recommendationIntent = new Intent(context,
                UpdateRecommendationsService.class);
        PendingIntent alarmIntent = PendingIntent.getService(context, 0,
                recommendationIntent, 0);
        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_DAY,
                AlarmManager.INTERVAL_HALF_HOUR,
                alarmIntent);
    }
}
@@ -221,10 +307,15 @@ public class BootupReceiver extends BroadcastReceiver {
  following sample code demonstrates how to add this configuration to the manifest:
</p>

<p class="code-caption">
  <a href="https://github.com/googlesamples/androidtv-Leanback/blob/master/app/src/main/AndroidManifest.xml" target="_blank">
  AndroidManifest.xml</a>
</p>
<pre>
&lt;manifest ... &gt;
  &lt;application ... &gt;
    &lt;receiver android:name=&quot;.BootupReceiver&quot; android:enabled=&quot;true&quot;
    &lt;receiver android:name=&quot;com.example.android.tvleanback.BootupActivity&quot;
              android:enabled=&quot;true&quot;
              android:exported=&quot;false&quot;&gt;
      &lt;intent-filter&gt;
        &lt;action android:name=&quot;android.intent.action.BOOT_COMPLETED&quot;/&gt;
@@ -234,7 +325,7 @@ public class BootupReceiver extends BroadcastReceiver {
&lt;/manifest&gt;
</pre>

<p class="important">
<p class="note">
  <strong>Important:</strong> Receiving a boot completed notification requires that your app
  requests the {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission.
  For more information, see {@link android.content.Intent#ACTION_BOOT_COMPLETED}.