With the Android O release, Android TV has a new UI, called the TV Launcher, or simply the launcher. The launcher displays recommended content as a table of channels and programs. Each row is a channel. A channel contains cards for every program available on that channel:

This document demonstrates how to add channels and programs to the launcher, update content, handle user actions, and provide the best experience for your users. (If you'd like to dig deeper into the API, try the TV Launcher codelab and watch the I/O 2017 Android TV session.)
The launcher UI
Apps can create new channels, add, remove, and update the programs in a channel, and control the order of programs in a channel. For example an app can create a channel called "What's New" and show cards for newly available programs.
Apps cannot control the order in which channels appear in the launcher. When your app creates a new channel, the launcher adds it to the bottom of the channel list. The user can reorder, hide, and show channels.
The Watch Next channel
The first channel that appears in the launcher is labeled "Watch Next." The system creates and maintains this channel. Your app cannot move, remove, or hide this channel. It can add programs to this channel: programs that the user marked as interesting, stopped watching in the middle, or that are related to the content the user is watching (for instance, the next episode in a series or next season of a show).
App channels
The channels that your app creates all follow this life cycle:
- User discovers a channel in your app and requests to add it to the launcher.
- App creates the channel and adds it to the
TvProvider(at this point the channel is not visible). - App asks the system to display the channel.
- System asks user to approve the new channel.
- New channel appears in the last row of the launcher.
The default channel
Your app can offer any number of channels for the user to add to the launcher. The user usually has to select and approve each channel before it appears in the launcher. Every app has the option of creating one default channel. The default channel is special because it automatically appears in the launcher; the user does not have to explicitly request it.
Prerequisites
The Android TV launcher uses Android's TvProvider APIs to manage the channels and programs that your app creates.
To access the provider's data, add the following permissions to your app's manifest:
<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
The TvProvider support library makes it easier to use the provider. Add it to the dependencies in your build.gradle file:
compile 'com.android.support:support-tv-provider:26.0.0-beta1'
To work with channels and programs, be sure to include these support library imports in your program:
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;
Channels
The first channel your app creates becomes its default channel. The default channel automatically appears in the launcher. All other channels you create must be selected and accepted by the user before they can appear in the launcher.
Creating a channel
Your app should ask the system to show newly added channels only when it is running in the foreground. This prevents your app from displaying a dialog requesting approval to add your channel while the user is running a different app. If you try to add a channel while running in the background, the activity's onActivityResult() method returns the status code RESULT_CANCELED.
To create a channel, follow these steps:
- Create a channel builder and set its attributes:
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);The channel type must be
TYPE_PREVIEW. Add more attributes as required. - Insert the channel into the provider:
Uri channelUri = context.getContentResolver().insert( TvContractCompat.Channels.CONTENT_URI, builder.build().toContentValues());You need to save the channel ID in order to add programs to the channel later. Extract the channel ID from the returned URI:
long channelId = ContentUris.parseId(channelUri); - You must add a logo for your channel. Use a
UriorBitmap:// 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);The logo icon should be 80dp x 80dp, and it must be opaque. It is displayed under a circular mask:

- Create the default channel (optional):
When your app creates its first channel, you can make it the default channel so it appears in the launcher immediately without any user action:
TvContractCompat.requestChannelBrowsable(context, channelId);Any other channels you create aren't visible until the user explicitly selects them.
Updating a channel
Updating channels is very similar to creating them.
Use another Channel.Builder to set the attributes that need to change.
Use the ContentResolver to update the channel. Use the channel ID that you saved when the channel was originally added:
context.getContentResolver().update(TvContractCompat.buildChannelUri(channelId),
builder.build().toContentValues(), null, null);
To update a channel's logo, use storeChannelLogo().
Deleting a channel
context.getContentResolver().delete(TvContractCompat.buildChannelUri(channelId), null, null);
Programs
Adding programs to an app channel
Create a PreviewProgram.Builder and set its attributes:
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);
Add more attributes depending on the type of program. (To see the attributes available for each type of program, refer to the tables below.)
Insert the program into the provider:
Uri programUri = context.getContentResolver().insert(TvContractCompat.PreviewPrograms.CONTENT_URI,
builder.build().toContentValues());
Retrieve the program ID for later reference:
long programId = ContentUris.parseId(programUri);
Adding programs to the Watch Next channel
Inserting programs into the Watch Next channel is the same as inserting programs into your own channel. There are four types of programs; select the appropriate type:
| Type | Notes |
|---|---|
| WATCH_NEXT_TYPE_CONTINUE | Inserted by the app when the user stops in the middle of watching content. |
| WATCH_NEXT_TYPE_NEXT | Inserted by the app to suggest the next available program in a series the user is watching. For example, if the user is watching episode 3 of a series, the app can suggest that they watch episode 4 next. |
| WATCH_NEXT_TYPE_NEW | Inserted by the app when new content is available (new episode in a series or a new season). For example, the user is watching episode number 5 from a series and episode 6 becomes available for watching. |
| WATCH_NEXT_TYPE_WATCHLIST | Inserted by the system or the app when the user indicates that they want to save a specific program. |
Use a WatchNextProgram.Builder:
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());
Use TvContractCompat.buildWatchNextProgramUri(long watchNextProgramId) to create the Uri you need to update a Watch Next program.
When the user adds a program to the Watch Next channel, the system copies the program to the channel. It sends the intent TvContractCompat.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT to notify the app that the program has been added. The intent includes two extras: the program ID that was copied and the program ID created for the program in the Watch Next channel.
Updating a program
You can change a program's information. For example, you may want to update the rental price for a the movie, or update a progress bar showing how much of a program the user has watched.
Use a PreviewProgram.Builder to set the attributes you need to change,
then call getContentResolver().update to update the program. Specify the program ID that you saved when the program was originally added:
context.getContentResolver().update(TvContractCompat.buildPreviewProgramUri(programId),
builder.build().toContentValues(), null, null);
Deleting a program
context.getContentResolver().delete(TvContractCompat.buildPreviewProgramUri(programId), null, null);
Handling user actions
Your app can help users discover content by providing a UI to display and add channels. Your app should also handle interactions with your channels after they appear in the launcher.
Discovering and adding channels
Your app can provide a UI element that lets the user select and add its channels (for example, a button that asks to add the channel). After the user requests a specific channel, execute this code to get the user's permission to add it to the launcher UI:
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
}
The system displays a dialog asking the user to approve the channel.
Handle the result of the request in the onActivityResult method of your activity (Activity.RESULT_CANCELED or Activity.RESULT_OK).
TV launcher events
When the user interacts with the programs/channels published by the app, the launcher sends intents to the app:
- The launcher sends the
Uristored in the APP_LINK_INTENT_URI attribute of a channel to the app when the user selects the channel's logo. The app should just launch its main UI or a view related to the selected channel. - The launcher sends the
Uristored in the INTENT_URI attribute of a program to the app when the user selects a program. The app should play the selected content. - The user can indicate that they are no longer interested in a program and want it removed from the launcher's UI. The system removes the program from the UI and sends the app that owns the program an intent (android.media.tv.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED or android.media.tv.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED) with the program's ID. The app should remove the program from the provider and should NOT reinsert it.
Make sure to create intent filters for all the Uris that the launcher sends for user interactions; for example:
<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>
Best practices
- Your app should insert its default channel and present content to the user as soon as possible. You can make this happen by
adding a
BroadcastReceiverthat listens for theandroid.media.tv.action.INITIALIZE_PROGRAMS"wakeup" intent that the system sends after it installs the app. In rare cases, your app might receive the "wakeup" broadcast at the same time the user starts it. Your code should not try to add the default channel more than once. - When your app is not in the foreground and you need to update a channel or a program, use the
JobSchedulerto schedule the work (see: JobScheduler and JobService). - The system can revoke your app's provider permissions if your app misbehaves (for example: continuously spamming the provider with data). Make sure you wrap the code that accesses the provider with try-catch clauses to handle security exceptions.
-
Before updating programs and channels, query the provider for the data you need to update and reconcile the data. For example, there is no need to update a program that the user wants removed from the UI. Use a background job that inserts/updates your data into the provider after querying for the existing data and then requesting approval for your channels. You can run this job when the app starts and whenever the app needs to update its data.
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 unique Uris for all images (logos, icons, content images). Be sure to use a different Uri when you update an image. All images are cached. If you do not change the Uri when you change the image, the old image will continue to appear.
- Remember that WHERE clauses are not allowed and calls to the providers with WHERE clauses will throw a security exception.
Attributes
This section describes the channel and program attributes separately.
Channel attributes
You must specify these attributes for every channel:
| Attribute | Notes |
|---|---|
| TYPE | set to TYPE_PREVIEW. |
| DISPLAY_NAME | set to the name of the channel. |
| APP_LINK_INTENT_URI | When the user selects the channel's logo the system sends an intent to start an activity that presents content relevant to the channel. Set this attribute to the Uri used in the intent filter for that activity. |
In addition, a channel also has six fields reserved for internal app usage. These fields can be used to store keys or other values that can help the app map the channel to its internal data structure:
- INTERNAL_PROVIDER_ID
- INTERNAL_PROVIDER_DATA
- INTERNAL_PROVIDER_FLAG1
- INTERNAL_PROVIDER_FLAG2
- INTERNAL_PROVIDER_FLAG3
- INTERNAL_PROVIDER_FLAG4
Program attributes
Video program attributes
Attributes for video programs depend on the type of the program. Video programs can be one of these types:
- TYPE_MOVIE
- TYPE_TV_SERIES
- TYPE_TV_SEASON
- TYPE_TV_EPISODE
- TYPE_CLIP
- TYPE_EVENT
- TYPE_CHANNEL
Attributes fall into two groups: required attributes and optional attributes.
Required video attributes
You must specify these attributes for all types of video programs:
- INTENT_URI
- CHANNEL_ID
- TITLE
- POSTER_ART_ASPECT_RATIO
- POSTER_ART_URI
These attributes are required for some types of video programs and are optional for others. When an attribute is optional, it's enclosed in parenthesis:
| Attribute | Movie | TV Series | TV Season | TV Episode | Clip | Event | Channel |
|---|---|---|---|---|---|---|---|
| CONTENT_RATING | ✔ | ✔ | ✔ | ✔ | (✔) | (✔) | (✔) |
| DURATION_MILLIS | ✔ | ✔ | ✔ | (✔) | |||
| EPISODE_DISPLAY_NUMBER | ✔ | ||||||
| SEASON_DISPLAY_NUMBER | ✔ | ✔ |
Optional video attributes
These optional attributes are available, depending on the program's type:
| Attribute | Movie | TV Series | TV Season | TV Episode | Clip | Event | Channel |
|---|---|---|---|---|---|---|---|
| AUTHOR | ✔ | ||||||
| AVAILABILITY | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| CANONICAL_GENRE | ✔ | ✔ | ✔ | ✔ | |||
| EPISODE_TITLE | ✔ | ||||||
| INTERACTION_COUNT | ✔ | ✔ | |||||
| INTERACTION_TYPE | ✔ | ✔ | |||||
| INTERNAL_PROVIDER_ID | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| ITEM_COUNT | ✔ | ✔ | |||||
| LIVE | ✔ | ✔ | ✔ | ✔ | ✔ | ||
| LOGO_URI | ✔ | ✔ | |||||
| OFFER_PRICE | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| PREVIEW_VIDEO_URI | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| RELEASE_DATE | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | |
| REVIEW_RATING | ✔ | ✔ | ✔ | ✔ | ✔ | ||
| REVIEW_RATING_STYLE | ✔ | ✔ | ✔ | ✔ | ✔ | ||
| SHORT_DESCRIPTION | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| STARTING_PRICE | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| THUMBNAIL_ASPECT_RATIO | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| THUMBNAIL_URI | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| VIDEO_HEIGHT | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| VIDEO_WIDTH | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
| WEIGHT | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ |
Preview images
The recommended sizes for preview images are:
| Aspect Ratio | Width | Height |
| 16:9 | 272dp | 153dp |
| 3:2 | 229.5dp | 153dp |
| 4:3 | 204dp | 153dp |
| 1:1 | 153dp | 153dp |
| 2:3 | 102dp | 153dp |
For best quality, preview videos should be 16:9 or 4:3 and at least the sizes specified in the table above. The logo should also be opaque for the best user experience.
You can specify the exact preview video sizes using VIDEO_WIDTH and VIDEO_HEIGHT.
Audio program attributes
Attributes for audio programs depend on the type of the program. Audio programs can be one of these types:
- TYPE_TRACK
- TYPE_ALBUM
- TYPE_ARTIST
- TYPE_PLAYLIST
- TYPE_STATION
Attributes fall into two groups: required attributes and optional attributes.
Required audio attributes
You must specify these attributes for all types of audio programs:
- INTENT_URI
- CHANNEL_ID
- POSTER_ART_URI
- POSTER_ART_ASPECT_RATIO
- TITLE
In addition, DURATION_MILLIS is required for audio tracks.
Optional audio attributes
These optional attributes are available, depending on the program's type:
| Attribute | Track | Album | Artist | Playlist | Station |
|---|---|---|---|---|---|
| AUTHOR | ✔ | ✔ | ✔ | ||
| AVAILABILITY | ✔ | ✔ | ✔ | ✔ | ✔ |
| INTERACTION_COUNT | ✔ | ✔ | ✔ | ||
| INTERACTION_TYPE | ✔ | ✔ | ✔ | ||
| INTERNAL_PROVIDER_ID | ✔ | ✔ | ✔ | ✔ | ✔ |
| LIVE | ✔ | ||||
| LOGO_URI | ✔ | ||||
| OFFER_PRICE | ✔ | ✔ | ✔ | ✔ | ✔ |
| PREVIEW_VIDEO_URI | ✔ | ✔ | ✔ | ✔ | ✔ |
| RELEASE_DATE | ✔ | ✔ | |||
| SHORT_DESCRIPTION | ✔ | ✔ | ✔ | ✔ | ✔ |
| STARTING_PRICE | ✔ | ✔ | ✔ | ✔ | ✔ |
| THUMBNAIL_ASPECT_RATIO | ✔ | ✔ | ✔ | ✔ | ✔ |
| THUMBNAIL_URI | ✔ | ✔ | ✔ | ✔ | ✔ |
| VIDEO_HEIGHT | ✔ | ✔ | ✔ | ✔ | ✔ |
| VIDEO_WIDTH | ✔ | ✔ | ✔ | ✔ | ✔ |
| WEIGHT | ✔ | ✔ | ✔ | ✔ | ✔ |
Watch Next program attributes
When you add an audio or video program to the Watch Next channel, you must include the following attributes, in addition to the required and optional attributes for audio/video:
| Attribute | Notes |
|---|---|
| WATCH_NEXT_TYPE | Select one of the Watch Next channel program types. |
| LAST_ENGAGEMENT_TIME_UTC_MILLIS | The time the user/app last engaged with the program. |
| LAST_PLAYBACK_POSITION_MILLIS | Only required for WATCH_NEXT_TYPE_CONTINUE |
| DURATION_MILLIS | Only required for WATCH_NEXT_TYPE_CONTINUE |