diff options
| author | 2015-04-01 21:25:53 +0000 | |
|---|---|---|
| committer | 2015-04-01 21:25:53 +0000 | |
| commit | a03245a343fe7906e4459b6a4dfcc41f60c56fd6 (patch) | |
| tree | 5f0dc1d42b3eafd31dbd87fd97e2d84f137eb7c1 | |
| parent | 92aa846daf64119d5951453c0298f83115f26d48 (diff) | |
| parent | 4fc3d3ff404c777e07616b704c68176d9060c4e4 (diff) | |
am 4fc3d3ff: Merge "docs: Building Live TV Apps" into lmp-docs
* commit '4fc3d3ff404c777e07616b704c68176d9060c4e4':
docs: Building Live TV Apps
| -rw-r--r-- | docs/html/images/tv/channel-info.png | bin | 0 -> 33686 bytes | |||
| -rw-r--r-- | docs/html/images/tv/do-not-attempt.png | bin | 0 -> 199961 bytes | |||
| -rw-r--r-- | docs/html/images/tv/prog-guide.png | bin | 0 -> 77929 bytes | |||
| -rw-r--r-- | docs/html/images/tv/tvinput-life.png | bin | 0 -> 39787 bytes | |||
| -rw-r--r-- | docs/html/training/training_toc.cs | 19 | ||||
| -rw-r--r-- | docs/html/training/tv/tif/channel.jd | 239 | ||||
| -rw-r--r-- | docs/html/training/tv/tif/index.jd | 28 | ||||
| -rw-r--r-- | docs/html/training/tv/tif/tvinput.jd | 177 | ||||
| -rw-r--r-- | docs/html/training/tv/tif/ui.jd | 304 |
9 files changed, 761 insertions, 6 deletions
diff --git a/docs/html/images/tv/channel-info.png b/docs/html/images/tv/channel-info.png Binary files differnew file mode 100644 index 000000000000..5a480781dc04 --- /dev/null +++ b/docs/html/images/tv/channel-info.png diff --git a/docs/html/images/tv/do-not-attempt.png b/docs/html/images/tv/do-not-attempt.png Binary files differnew file mode 100644 index 000000000000..18a8775be0d9 --- /dev/null +++ b/docs/html/images/tv/do-not-attempt.png diff --git a/docs/html/images/tv/prog-guide.png b/docs/html/images/tv/prog-guide.png Binary files differnew file mode 100644 index 000000000000..caa227875360 --- /dev/null +++ b/docs/html/images/tv/prog-guide.png diff --git a/docs/html/images/tv/tvinput-life.png b/docs/html/images/tv/tvinput-life.png Binary files differnew file mode 100644 index 000000000000..fc53f89f6a61 --- /dev/null +++ b/docs/html/images/tv/tvinput-life.png diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 11ae1a6f2d67..2873b5b3ad1d 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -998,10 +998,25 @@ include the action bar on devices running Android 2.1 or higher." Building TV Games</a> </li> - <li> - <a href="<?cs var:toroot ?>training/tv/tif/index.html" + <li class="nav-section"> + <div class="nav-section-header"> + <a href="<?cs var:toroot ?>training/tv/tif/index.html" description="How to build Live TV apps."> Building Live TV Apps</a> + </div> + <ul> + <li> + <a href="<?cs var:toroot ?>training/tv/tif/tvinput.html"> + Developing a TV Input Service</a> + <li> + <a href="<?cs var:toroot ?>training/tv/tif/channel.html"> + Working with Channel Data</a> + </li> + <li> + <a href="<?cs var:toroot ?>training/tv/tif/ui.html"> + Managing User Interaction</a> + </li> + </ul> </li> <li> diff --git a/docs/html/training/tv/tif/channel.jd b/docs/html/training/tv/tif/channel.jd new file mode 100644 index 000000000000..999f1ca9c5c5 --- /dev/null +++ b/docs/html/training/tv/tif/channel.jd @@ -0,0 +1,239 @@ +page.title=Working with Channel Data +page.tags=tv, tif +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#permission">Get Permission</a></li> + <li><a href="#register">Register Channels in the Database</a></li> + <li><a href="#update">Update Channel Data</a></li> + </ol> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> +</div> +</div> + +<p>Your TV input must provide Electronic Program Guide (EPG) data for at least one channel in its +setup activity. You should also periodically update that data, with consideration for the size of +the update and the processing thread that handles it. This lesson discusses creating and updating +channel and program data on the system database with these considerations in mind.</p> + +<p> </p> + +<h2 id="permission">Get Permission</h2> + +<p>In order for your TV input to work with EPG data, it must declare the +read and write permissions in its Android manifest file as follows:</p> + +<pre> +<uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" /> +<uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> +</pre> + +<h2 id="register">Register Channels in the Database</h2> + +<p>The Android TV system database maintains records of channel data for TV inputs. In your setup +activity, for each of your channels, you must map your channel data to the following fields of the +{@link android.media.tv.TvContract.Channels} class:</p> + +<ul> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NAME} - the displayed name of the + channel</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NUMBER} - the displayed channel + number</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_INPUT_ID} - the ID of the TV input service</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_SERVICE_TYPE} - the channel's service type</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_TYPE} - the channel's broadcast standard + type</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_VIDEO_FORMAT} - the default video format + for the channel</li> +</ul> + +<p>Although the TV input framework is generic enough to handle both traditional broadcast and +over-the-top (OTT) content without any distinction, you may want to define the following columns in +addition to those above to better identify traditional broadcast channels:</p> + +<ul> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_ORIGINAL_NETWORK_ID} - the television + network ID</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_SERVICE_ID} - the service ID</li> + <li>{@link android.media.tv.TvContract.Channels#COLUMN_TRANSPORT_STREAM_ID} - the transport stream + ID</li> +</ul> + +<p>For internet streaming based TV inputs, assign your own values to the above accordingly so that +each channel can be identified uniquely.</p> + +<p>Pull your channel metadata (in XML, JSON, or whatever) from your backend server, and in your setup +activity map the values to the system database as follows:</p> + +<pre> +ContentValues values = new ContentValues(); + +values.put(Channels.COLUMN_DISPLAY_NUMBER, channel.mNumber); +values.put(Channels.COLUMN_DISPLAY_NAME, channel.mName); +values.put(Channels.COLUMN_ORIGINAL_NETWORK_ID, channel.mOriginalNetworkId); +values.put(Channels.COLUMN_TRANSPORT_STREAM_ID, channel.mTransportStreamId); +values.put(Channels.COLUMN_SERVICE_ID, channel.mServiceId); +values.put(Channels.COLUMN_VIDEO_FORMAT, channel.mVideoFormat); + +Uri uri = context.getContentResolver().insert(TvContract.Channels.CONTENT_URI, values); +</pre> + +<p>In the example above, <code>channel</code> is an object which holds channel metadata from the +backend server.</p> + +<h3 id="art">Present Channel and Program Information</h2> + +<p>The system TV app presents channel and program information to users as they flip through channels, +as shown in figure 1. To make sure the channel and program information works with the system TV app's +channel and program information presenter, follow the guidelines below.</p> + +<ol> +<li><strong>Channel number</strong> ({@link android.media.tv.TvContract.Channels#COLUMN_DISPLAY_NUMBER}) +<li><strong>Icon</strong> +(<a href="guide/topics/manifest/application-element.html#icon"><code>android:icon</code></a> in the +TV input's manifest)</li> +<li><strong>Program description</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_SHORT_DESCRIPTION}) +<li><strong>Program title</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_TITLE})</li> +<li><strong>Channel logo</strong> ({@link android.media.tv.TvContract.Channels.Logo}) + <ul> + <li>Use the color #EEEEEE to match the surrounding text</li> + <li>Don't include padding + </ul></li> +<li><strong>Poster art</strong> ({@link android.media.tv.TvContract.Programs#COLUMN_POSTER_ART_URI}) + <ul> + <li>Aspect ratio between 16:9 and 4:3</li> + </ul> +</ol> + +<img src="{@docRoot}images/tv/channel-info.png" id="figure1"> +<p class="img-caption"> + <strong>Figure 1.</strong> The system TV app channel and program information presenter. +</p> + +<p>The system TV app provides the same information through the program guide, including poster art, +as shown in figure 2.</p> + +<img src="{@docRoot}images/tv/prog-guide.png" id="figure2"> +<p class="img-caption"> + <strong>Figure 2.</strong> The system TV app program guide. +</p> + +<h2 id="update">Update Channel Data</h2> + +<p>When updating existing channel data, use the +{@link android.content.ContentProvider#update(android.net.Uri, android.content.ContentValues, +java.lang.String, java.lang.String[]) update()} +method instead of deleting and re-adding the data. You can identify the current version of the data +by using {@link android.media.tv.TvContract.Channels#COLUMN_VERSION_NUMBER Channels.COLUMN_VERSION_NUMBER} +and {@link android.media.tv.TvContract.Programs#COLUMN_VERSION_NUMBER Programs.COLUMN_VERSION_NUMBER} +when choosing the records to update.</p> + +<p class="note"><strong>Note:</strong> Adding channel data to the {@link android.content.ContentProvider} +can take time. Only add current programs (those within two hours of the current time) when you update, +and use a <a href="{@docRoot}training/sync-adapters/creating-sync-adapter.html">Sync Adapter</a> to +update the rest of the channel data in the background. See the <a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java"> +Android TV Live TV Sample App</a> for an example.</p> + +<h3 id="batch">Batch Loading Channel Data</h3> + +<p>When updating the system database with a large amount of channel data, use the {@link android.content.ContentResolver} +{@link android.content.ContentResolver#applyBatch applyBatch()} +or +{@link android.content.ContentResolver#bulkInsert(android.net.Uri, android.content.ContentValues[]) bulkInsert()} +method. Here's an example using {@link android.content.ContentResolver#applyBatch applyBatch()}:<p> + +<pre> +ArrayList<ContentProviderOperation> ops = new ArrayList<>(); +int programsCount = mChannelInfo.mPrograms.size(); +for (int j = 0; j < programsCount; ++j) { + ProgramInfo program = mChannelInfo.mPrograms.get(j); + ops.add(ContentProviderOperation.newInsert( + TvContract.Programs.CONTENT_URI) + .withValues(programs.get(j)) + .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS, + programStartSec * 1000) + .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS, + (programStartSec + program.mDurationSec) * 1000) + .build()); + programStartSec = programStartSec + program.mDurationSec; + if (j % 100 == 99 || j == programsCount - 1) { + try { + <strong>getContentResolver().applyBatch(TvContract.AUTHORITY, ops);</strong> + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Failed to insert programs.", e); + return; + } + ops.clear(); + } +} +</pre> + +<h3 id="async">Processing Channel Data Asynchronously</h3> + +<p>Data manipulation, such as fetching a stream from the server or accessing the database, should +not block the UI thread. Using an {@link android.os.AsyncTask} is one +way to perform updates asynchronously. For example, when loading channel info from a backend server, +you can use {@link android.os.AsyncTask} as follows:</p> + +<pre> +private static class LoadTvInputTask extends AsyncTask<Uri, Void, Void>> { + + private Context mContext; + + public LoadTvInputTask(Context context) { + mContext = context; + } + + @Override + protected Void doInBackground(Uri... uris) { + try { + fetchUri(uris[0]); + } catch (IOException e) { + Log.d(“LoadTvInputTask”, “fetchUri error”); + } + return null; + } + + private void fetchUri(Uri videoUri) throws IOException { + InputStream inputStream = null; + try { + inputStream = mContext.getContentResolver().openInputStream(videoUri); + XmlPullParser parser = Xml.newPullParser(); + try { + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(inputStream, null); + sTvInput = ChannelXMLParser.parseTvInput(parser); + sSampleChannels = ChannelXMLParser.parseChannelXML(parser); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } +} +</pre> + +<p>If you need to update EPG data on a regular basis, consider using +a <a href="{@docRoot}training/sync-adapters/creating-sync-adapter.html"> +Sync Adapter</a> or {@link android.app.job.JobScheduler} to run the update process during idle time, +such as every day at 3:00 a.m. See the <a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/java/com/example/android/sampletvinput/syncadapter/SyncAdapter.java"> +Android TV live TV sample app</a> for an example.</p> + +<p>Other techniques to separate the data update tasks from the UI thread include using the +{@link android.os.HandlerThread} class, or you may implement your own using {@link android.os.Looper} +and {@link android.os.Handler} classes. See <a href="{@docRoot}guide/components/processes-and-threads.html"> +Processes and Threads</a> for more information.</p>
\ No newline at end of file diff --git a/docs/html/training/tv/tif/index.jd b/docs/html/training/tv/tif/index.jd index 9c10850e4cdf..57392940bee2 100644 --- a/docs/html/training/tv/tif/index.jd +++ b/docs/html/training/tv/tif/index.jd @@ -1,17 +1,26 @@ page.title=Building Live TV Apps page.tags=tv, tif helpoutsWidget=true -page.article=true +startpage=true @jd:body <div id="tb-wrapper"> <div id="tb"> + <h2>Dependencies and Prerequisites</h2> + <ul> + <li>Android 5.0 (API level 21) or higher</li> + </ul> <h2>You should also read</h2> <ul> <li><a href="{@docRoot}reference/android/media/tv/package-summary.html"> android.media.tv</a></li> </ul> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> </div> </div> @@ -44,6 +53,17 @@ page.article=true Building a TV input service for your content can help make it more accessible on TV devices. </p> -<p>For more information about TV Input Framework, see the -<a href="{@docRoot}reference/android/media/tv/package-summary.html">android.media.tv</a> -reference.</p> +<h2>Topics</h2> + +<dl> + <dt><b><a href="tvinput.html">Developing a TV Input Service</a></b></dt> + <dd>Learn how to develop a TV input service, which works with the system TV app.</dd> + + <dt><b><a href="channel.html">Working with Channel Data</a></b></dt> + <dd>Learn how to describe channel and program data for the system.</dd> + + <dt><b><a href="ui.html">Managing User Interaction</a></b></dt> + <dd>Learn how to present overlays, manage content availability, and handle content selection.</dd> +</dl> + + diff --git a/docs/html/training/tv/tif/tvinput.jd b/docs/html/training/tv/tif/tvinput.jd new file mode 100644 index 000000000000..91f8ded007bd --- /dev/null +++ b/docs/html/training/tv/tif/tvinput.jd @@ -0,0 +1,177 @@ +page.title=Developing a TV Input Service +page.tags=tv, tif +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#manifest">Declare Your TV Input Service in the Manifest</a></li> + <li><a href="#tvinput">Define Your TV Input Service</a></li> + <li><a href="#setup">Define Setup and Settings Activities</a></li> + </ol> + <h2>You should also read</h2> + <ul> + <li><a href="{@docRoot}reference/android/media/tv/package-summary.html"> + android.media.tv</a></li> + <li><a class="external-lin" href="http://source.android.com/devices/tv/index.html"> + TV Input Framework</a></li> + </ul> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> +</div> +</div> + +<p>A TV input service represents a media stream source, and lets you present your media content in a +linear, broadcast TV fashion as channels and programs. With the TV input service, you can provide +parental controls, program guide information, and content ratings. The TV input service works +with the Android system TV app, developed for the device and immutable by third-party apps, which +ultimately controls and presents content on the TV. See +<a class="external-link" href="http://source.android.com/devices/tv/index.html"> +TV Input Framework</a> for more information about the framework architecture and its components.</p> + +<p>To develop a TV input service, you implement the following components:</p> + +<ul> + <li>{@link android.media.tv.TvInputService} provides long-running and background availability for + the TV input</li> + <li>{@link android.media.tv.TvInputService.Session} maintains the TV input state and communicates + with the hosting app</li> + <li>{@link android.media.tv.TvContract} describes the channels and programs available to the TV + input</li> + <li>{@link android.media.tv.TvContract.Channels} represents information about a TV channel</li> + <li>{@link android.media.tv.TvContract.Programs} describes a TV program with data such as program + title and start time</li> + <li>{@link android.media.tv.TvTrackInfo} represents an audio, video, or subtitle track</li> + <li>{@link android.media.tv.TvContentRating} describes a content rating, allows for custom content + rating schemes</li> + <li>{@link android.media.tv.TvInputManager} provides an API to the system TV app and manages + the interaction with TV inputs and apps</li> +</ul> + +<h2 id="manifest">Declare Your TV Input Service in the Manifest</h2> + +<p>Your app manifest must declare your {@link android.media.tv.TvInputService}. Within that +declaration, specify the {@link android.Manifest.permission#BIND_TV_INPUT} permission to allow the +service to connect the TV input to the system. A system service (<code>TvInputManagerService</code>) +performs the binding and has that permission. The system TV app sends requests to TV input services +via the {@link android.media.tv.TvInputManager} interface. The service declaration must also +include an intent filter that specifies the {@link android.media.tv.TvInputService} +as the action to perform with the intent. Also within the service declaration, declare the service +meta data in a separate XML resource. The service declaration, the intent filter and the service +meta data are described in the following example.</p> + +<pre> +<service android:name="com.example.sampletvinput.SampleTvInput" + android:label="@string/sample_tv_input_label" + android:permission="android.permission.BIND_TV_INPUT"> + <intent-filter> + <action android:name="android.media.tv.TvInputService" /> + </intent-filter> + <meta-data android:name="android.media.tv.input" + android:resource="@xml/sample_tv_input" /> +</service> +</pre> + +<p>Define the service meta data in separate XML file, as shown in the following example. The service +meta data must include a setup interface that describes the TV input's initial configuration and +channel scan. Also, the service meta data may (optionally) describe a settings activity for users to +modify the TV input's behavior. The service meta data file is located in the XML resources directory +for your application and must match the name of the resource in the manifest. Using the example +manifest entries above, you would create an XML file in the location +<code>res/xml/sample_tv_input.xml</code>, with the following contents:</p> + +<pre> +<tv-input xmlns:android="http://schemas.android.com/apk/res/android" + <!-- Required: activity for setting up the input --> + android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" + <!-- Optional: activity for controlling the settings --> + android:settingsActivity="com.example.sampletvinput.SampleTvInputSettingsActivity" /> +</pre> + +<h2 id="tvinput">Define Your TV Input Service</h2> + +<div class="figure"> +<img id="tvinputlife" src="{@docRoot}images/tv/tvinput-life.png" alt=""/> +<p class="img-caption"><strong>Figure 1.</strong>TvInputService lifecycle.</p> +</div> + +<p>For your service, you extend the {@link android.media.tv.TvInputService} class. A +{@link android.media.tv.TvInputService} implementation is a +<a href="{@docRoot}guide/components/bound-services.html">bound service</a> where the system service +(<code>TvInputManagerService</code>) is the client that binds to it. The service life cycle methods +you need to implement are illustrated in figure 1.</p> + +<p>The {@link android.app.Service#onCreate()} method initializes and starts the +{@link android.os.HandlerThread} which provides a process thread separate from the UI thread to +handle system-driven actions. In the following example, the {@link android.app.Service#onCreate()} +method initializes the {@link android.view.accessibility.CaptioningManager} and prepares to handle +the {@link android.media.tv.TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} +and {@link android.media.tv.TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} actions. These +actions describe system intents fired when the user changes the parental control settings, and when +there is a change on the list of blocked ratings.</p> + +<pre> +@Override +public void onCreate() { + super.onCreate(); + mHandlerThread = new HandlerThread(getClass() + .getSimpleName()); + mHandlerThread.start(); + mDbHandler = new Handler(mHandlerThread.getLooper()); + mHandler = new Handler(); + mCaptioningManager = (CaptioningManager) + getSystemService(Context.CAPTIONING_SERVICE); + + setTheme(android.R.style.Theme_Holo_Light_NoActionBar); + + mSessions = new ArrayList<BaseTvInputSessionImpl>(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(TvInputManager + .ACTION_BLOCKED_RATINGS_CHANGED); + intentFilter.addAction(TvInputManager + .ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED); + registerReceiver(mBroadcastReceiver, intentFilter); +} +</pre> + +<p> See <a href="{@docRoot}training/tv/tif/ui.html#control"> +Control Content</a> for more information about working with blocked content and providing +parental control. See {@link android.media.tv.TvInputManager} for more system-driven actions that +you may want to handle in your TV input service.</p> + +<p>The {@link android.media.tv.TvInputService} creates a +{@link android.media.tv.TvInputService.Session} that implements {@link android.os.Handler.Callback} +to handle player state changes. With {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) onSetSurface()}, +the {@link android.media.tv.TvInputService.Session} sets the {@link android.view.Surface} with the +video content. See <a href="{@docRoot}training/tv/tif/ui.html#surface">Integrate Player with Surface</a> +for more information about working with {@link android.view.Surface} to render video.</p> + +<p>The {@link android.media.tv.TvInputService.Session} handles the +{@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) onTune()} +event when the user selects a channel, and notifies the system TV app for changes in the content and +content meta data. These <code>notify()</code>code> methods are described in +<a href="{@docRoot}training/tv/tif/ui.html#control"> +Control Content</a> and <a href="training/tv/tif/ui.html#track">Handle Track Selection</a> further +in this training.</p> + +<h2 id="setup">Define Setup and Settings Activities</h2> + +<p>The system TV app works with the setup and settings activities you define for your TV input. The +setup activity is required and must provide at least one channel record for the system database. The +system TV app will invoke the setup activity when it cannot find a channel for the TV input. +<p>The setup activity describes to the system TV app the channels made available through the TV +input, as demonstrated in the next lesson, <a href="{@docRoot}training/tv/tif/channel.html">Creating +and Updating Channel Data</a>.</p> + +<p>The settings activity is optional. You can define a settings activity to turn on parental +controls, enable closed captions, set the display attributes, and so forth.</p> + + diff --git a/docs/html/training/tv/tif/ui.jd b/docs/html/training/tv/tif/ui.jd new file mode 100644 index 000000000000..6ead3dbb72f9 --- /dev/null +++ b/docs/html/training/tv/tif/ui.jd @@ -0,0 +1,304 @@ +page.title=Managing User Interaction +page.tags=tv, tif +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + <h2>This lesson teaches you to</h2> + <ol> + <li><a href="#surface">Integrate Player with Surface</a></li> + <li><a href="#overlay">Use an Overlay</a></li> + <li><a href="#control">Control Content</a></li> + <li><a href="#track">Handle Track Selection</a></li> + </ol> + <h2>Try It Out</h2> + <ul> + <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs"> + TV Input Service sample app</a></li> + </ul> +</div> +</div> + +<p>In the live TV experience the user changes channels and is presented with +channel and program information briefly before the information disappears. Other types of information, +such as messages ("DO NOT ATTEMPT AT HOME"), subtitles, or ads may need to persist. As with any TV +app, such information should not interfere with the program content playing on the screen.</p> + +<img src="{@docRoot}images/tv/do-not-attempt.png" id="figure1"> +<p class="img-caption"> + <strong>Figure 1.</strong> An overlay message in a live TV app. +</p> + +<p>Also consider whether certain program content should be presented, given the +content's rating and parental control settings, and how your app behaves and informs the user when +content is blocked or unavailable. This lesson describes how to develop your TV input's user +experience for these considerations.</p> + +<h2 id="surface">Integrate Player with Surface</h2> + +<p>Your TV input must render video onto a {@link android.view.Surface} object, which is passed by +the {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) TvInputService.Session.onSetSurface()} +method. Here's an example of how to use a {@link android.media.MediaPlayer} instance for playing +content in the {@link android.view.Surface} object:</p> + +<pre> +@Override +public boolean onSetSurface(Surface surface) { + if (mPlayer != null) { + mPlayer.setSurface(surface); + } + mSurface = surface; + return true; +} + +@Override +public void onSetStreamVolume(float volume) { + if (mPlayer != null) { + mPlayer.setVolume(volume, volume); + } + mVolume = volume; +} +</pre> + +<p>Similarly, here's how to do it using <a href="{@docRoot}guide/topics/media/exoplayer.html"> +ExoPlayer</a>:</p> + +<pre> +@Override +public boolean onSetSurface(Surface surface) { + if (mPlayer != null) { + mPlayer.sendMessage(mVideoRenderer, + MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, + surface); + } + mSurface = surface; + return true; +} + +@Override +public void onSetStreamVolume(float volume) { + if (mPlayer != null) { + mPlayer.sendMessage(mAudioRenderer, + MediaCodecAudioTrackRenderer.MSG_SET_VOLUME, + volume); + } + mVolume = volume; +} +</pre> + +<h2 id="overlay">Use an Overlay</h2> + +<p>Use an overlay to display subtitles, messages, ads or MHEG-5 data broadcasts. By default, the +overlay is disabled. You can enable it when you create the session by calling +{@link android.media.tv.TvInputService.Session#setOverlayViewEnabled(boolean) TvInputService.Session.setOverlayViewEnabled(true)}, +as in the following example:</p> + +<pre> +@Override +public final Session onCreateSession(String inputId) { + BaseTvInputSessionImpl session = onCreateSessionInternal(inputId); + session.setOverlayViewEnabled(true); + mSessions.add(session); + return session; +} +</pre> + +<p>Use a {@link android.view.View} object for the overlay, returned from {@link android.media.tv.TvInputService.Session#onCreateOverlayView() TvInputService.Session.onCreateOverlayView()}, as shown here:</p> + +<pre> +@Override +public View onCreateOverlayView() { + LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.overlayview, null); + mSubtitleView = (SubtitleView) view.findViewById(R.id.subtitles); + + // Configure the subtitle view. + CaptionStyleCompat captionStyle; + float captionTextSize = getCaptionFontSize(); + captionStyle = CaptionStyleCompat.createFromCaptionStyle( + mCaptioningManager.getUserStyle()); + captionTextSize *= mCaptioningManager.getFontScale(); + mSubtitleView.setStyle(captionStyle); + mSubtitleView.setTextSize(captionTextSize); + return view; +} +</pre> + +<p>The layout definition for the overlay might look something like this:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.exoplayer.text.SubtitleView + android:id="@+id/subtitles" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|center_horizontal" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginBottom="32dp" + android:visibility="invisible"/> +</FrameLayout> +</pre> + +<h2 id="control">Control Content</h2> + +<p>When the user selects a channel, your TV input handles the {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) +onTune()} callback in the {@link android.media.tv.TvInputService.Session} object. The system TV +app's parental controls determine what content displays, given the content rating. +The following sections describe how to manage channel and program selection using the +{@link android.media.tv.TvInputService.Session} <code>notify</code> methods that +communicate with the system TV app.</p> + +<h3 id="unavailable">Make Video Unavailable</h3> + +<p>When the user changes the channel, you want to make sure the screen doesn't display any stray +video artifacts before your TV input renders the content. When you call {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) TvInputService.Session.onTune()}, +you can prevent the video from being presented by calling {@link android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) TvInputService.Session.notifyVideoUnavailable()} +and passing the {@link android.media.tv.TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} constant, as +shown in the following example.</p> + +<pre> +@Override +public boolean onTune(Uri channelUri) { + if (mSubtitleView != null) { + mSubtitleView.setVisibility(View.INVISIBLE); + } + notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING); + mUnblockedRatingSet.clear(); + + mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable); + mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri); + mDbHandler.post(mPlayCurrentProgramRunnable); + return true; +} +</pre> + +<p>Then, when the content is rendered to the {@link android.view.Surface}, you call +{@link android.media.tv.TvInputService.Session#notifyVideoAvailable() TvInputService.Session.notifyVideoAvailable()} +to allow the video to display, like so:</p> + +<pre> +@Override +public void onDrawnToSurface(Surface surface) { + mFirstFrameDrawn = true; + notifyVideoAvailable(); +} +</pre> + +<p>This transition lasts only for fractions of a second, but presenting a blank screen is +visually better than allowing the picture to flash odd blips and jitters.</p> + +<p>See also, <a href="#surface">Integrate Player with Surface</a> for more information about working +with {@link android.view.Surface} to render video.</p> + +<h3 id="parental">Provide Parental Control</h3> + +<p>To determine if a given content is blocked by parental controls and content rating, you check the +{@link android.media.tv.TvInputManager} class methods, {@link android.media.tv.TvInputManager#isParentalControlsEnabled()} +and {@link android.media.tv.TvInputManager#isRatingBlocked(android.media.tv.TvContentRating)}. You +might also want to make sure the content's {@link android.media.tv.TvContentRating} is included in a +set of currently allowed content ratings. These considerations are shown in the following sample.</p> + +<pre> +private void checkContentBlockNeeded() { + if (mCurrentContentRating == null || !mTvInputManager.isParentalControlsEnabled() + || !mTvInputManager.isRatingBlocked(mCurrentContentRating) + || mUnblockedRatingSet.contains(mCurrentContentRating)) { + // Content rating is changed so we don't need to block anymore. + // Unblock content here explicitly to resume playback. + unblockContent(null); + return; + } + + mLastBlockedRating = mCurrentContentRating; + if (mPlayer != null) { + // Children restricted content might be blocked by TV app as well, + // but TIF should do its best not to show any single frame of blocked content. + releasePlayer(); + } + + notifyContentBlocked(mCurrentContentRating); +} +</pre> + +<p>Once you have determined if the content should or should not be blocked, notify the system TV +app by calling the +{@link android.media.tv.TvInputService.Session} method {@link android.media.tv.TvInputService.Session#notifyContentAllowed() notifyContentAllowed()} +or +{@link android.media.tv.TvInputService.Session#notifyContentBlocked(android.media.tv.TvContentRating) notifyContentBlocked()} +, as shown in the previous example.</p> + +<p>Use the {@link android.media.tv.TvContentRating} class to generate the system-defined string for +the {@link android.media.tv.TvContract.Programs#COLUMN_CONTENT_RATING} with the +<code><a href="{@docRoot}reference/android/media/tv/TvContentRating.html#createRating(java.lang.String, java.lang.String, java.lang.String, java.lang.String...)">TvContentRating.createRating()</a></code> +method, as shown here:</p> + +<pre> +TvContentRating rating = TvContentRating.createRating( + "com.android.tv", + "US_TV", + "US_TV_PG", + "US_TV_D", "US_TV_L"); +</pre> + +<h2 id="track">Handle Track Selection</h2> + +<p>The {@link android.media.tv.TvTrackInfo} class holds information about media tracks such +as the track type (video, audio, or subtitle) and so forth. </p> + +<p>The first time your TV input session is able to get track information, it should call +<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">TvInputService.Session.notifyTracksChanged()</a></code> with a list of all tracks to update the system TV app. When there +is a change in track information, call +<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">notifyTracksChanged()</a></code> +again to update the system. + +</p> + +<p>The system TV app provides an interface for the user to select a specific track if more than one +track is available for a given track type; for example, subtitles in different languages. Your TV +input responds to the +{@link android.media.tv.TvInputService.Session#onSelectTrack(int, java.lang.String) onSelectTrack()} +call from the system TV app by calling +{@link android.media.tv.TvInputService.Session#notifyTrackSelected(int, java.lang.String) notifyTrackSelected()} +, as shown in the following example. Note that when <code>null</code> +is passed as the track ID, this <em>deselects</em> the track.</p> + +<pre> +@Override +public boolean onSelectTrack(int type, String trackId) { + if (mPlayer != null) { + if (type == TvTrackInfo.TYPE_SUBTITLE) { + if (!mCaptionEnabled && trackId != null) { + return false; + } + mSelectedSubtitleTrackId = trackId; + if (trackId == null) { + mSubtitleView.setVisibility(View.INVISIBLE); + } + } + if (mPlayer.selectTrack(type, trackId)) { + notifyTrackSelected(type, trackId); + return true; + } + } + return false; +} +</pre> + + + + + + + |