1page.title=Managing User Interaction
2page.tags=tv, tif
3helpoutsWidget=true
4
5trainingnavtop=true
6
7@jd:body
8
9<div id="tb-wrapper">
10<div id="tb">
11  <h2>This lesson teaches you to</h2>
12  <ol>
13    <li><a href="#surface">Integrate Player with Surface</a></li>
14    <li><a href="#overlay">Use an Overlay</a></li>
15    <li><a href="#control">Control Content</a></li>
16    <li><a href="#track">Handle Track Selection</a></li>
17  </ol>
18  <h2>Try It Out</h2>
19  <ul>
20    <li><a class="external-link" href="https://github.com/googlesamples/androidtv-sample-inputs">
21      TV Input Service sample app</a></li>
22  </ul>
23</div>
24</div>
25
26<p>In the live TV experience the user changes channels and is presented with
27channel and program information briefly before the information disappears. Other types of information,
28such as messages ("DO NOT ATTEMPT AT HOME"), subtitles, or ads may need to persist. As with any TV
29app, such information should not interfere with the program content playing on the screen.</p>
30
31<img src="{@docRoot}images/tv/do-not-attempt.png" id="figure1">
32<p class="img-caption">
33  <strong>Figure 1.</strong> An overlay message in a live TV app.
34</p>
35
36<p>Also consider whether certain program content should be presented, given the
37content's rating and parental control settings, and how your app behaves and informs the user when
38content is blocked or unavailable. This lesson describes how to develop your TV input's user
39experience for these considerations.</p>
40
41<h2 id="surface">Integrate Player with Surface</h2>
42
43<p>Your TV input must render video onto a {@link android.view.Surface} object, which is passed by
44the {@link android.media.tv.TvInputService.Session#onSetSurface(android.view.Surface) TvInputService.Session.onSetSurface()}
45method. Here's an example of how to use a {@link android.media.MediaPlayer} instance for playing
46content in the {@link android.view.Surface} object:</p>
47
48<pre>
49&#64;Override
50public boolean onSetSurface(Surface surface) {
51    if (mPlayer != null) {
52        mPlayer.setSurface(surface);
53    }
54    mSurface = surface;
55    return true;
56}
57
58&#64;Override
59public void onSetStreamVolume(float volume) {
60    if (mPlayer != null) {
61        mPlayer.setVolume(volume, volume);
62    }
63    mVolume = volume;
64}
65</pre>
66
67<p>Similarly, here's how to do it using <a href="{@docRoot}guide/topics/media/exoplayer.html">
68ExoPlayer</a>:</p>
69
70<pre>
71&#64;Override
72public boolean onSetSurface(Surface surface) {
73    if (mPlayer != null) {
74        mPlayer.sendMessage(mVideoRenderer,
75                MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
76                surface);
77    }
78    mSurface = surface;
79    return true;
80}
81
82&#64;Override
83public void onSetStreamVolume(float volume) {
84    if (mPlayer != null) {
85        mPlayer.sendMessage(mAudioRenderer,
86                MediaCodecAudioTrackRenderer.MSG_SET_VOLUME,
87                volume);
88    }
89    mVolume = volume;
90}
91</pre>
92
93<h2 id="overlay">Use an Overlay</h2>
94
95<p>Use an overlay to display subtitles, messages, ads or MHEG-5 data broadcasts. By default, the
96overlay is disabled. You can enable it when you create the session by calling
97{@link android.media.tv.TvInputService.Session#setOverlayViewEnabled(boolean) TvInputService.Session.setOverlayViewEnabled(true)},
98as in the following example:</p>
99
100<pre>
101&#64;Override
102public final Session onCreateSession(String inputId) {
103    BaseTvInputSessionImpl session = onCreateSessionInternal(inputId);
104    session.setOverlayViewEnabled(true);
105    mSessions.add(session);
106    return session;
107}
108</pre>
109
110<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>
111
112<pre>
113&#64;Override
114public View onCreateOverlayView() {
115    LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
116    View view = inflater.inflate(R.layout.overlayview, null);
117    mSubtitleView = (SubtitleView) view.findViewById(R.id.subtitles);
118
119    // Configure the subtitle view.
120    CaptionStyleCompat captionStyle;
121    float captionTextSize = getCaptionFontSize();
122    captionStyle = CaptionStyleCompat.createFromCaptionStyle(
123            mCaptioningManager.getUserStyle());
124    captionTextSize *= mCaptioningManager.getFontScale();
125    mSubtitleView.setStyle(captionStyle);
126    mSubtitleView.setTextSize(captionTextSize);
127    return view;
128}
129</pre>
130
131<p>The layout definition for the overlay might look something like this:</p>
132
133<pre>
134&lt;?xml version="1.0" encoding="utf-8"?&gt;
135&lt;FrameLayout
136    xmlns:android="http://schemas.android.com/apk/res/android"
137    xmlns:tools="http://schemas.android.com/tools"
138
139    android:layout_width="match_parent"
140    android:layout_height="match_parent"&gt;
141
142    &lt;com.google.android.exoplayer.text.SubtitleView
143        android:id="@+id/subtitles"
144        android:layout_width="wrap_content"
145        android:layout_height="wrap_content"
146        android:layout_gravity="bottom|center_horizontal"
147        android:layout_marginLeft="16dp"
148        android:layout_marginRight="16dp"
149        android:layout_marginBottom="32dp"
150        android:visibility="invisible"/&gt;
151&lt;/FrameLayout&gt;
152</pre>
153
154<h2 id="control">Control Content</h2>
155
156<p>When the user selects a channel, your TV input handles the {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri)
157onTune()} callback in the {@link android.media.tv.TvInputService.Session} object. The system TV
158app's parental controls determine what content displays, given the content rating.
159The following sections describe how to manage channel and program selection using the
160{@link android.media.tv.TvInputService.Session} <code>notify</code> methods that
161communicate with the system TV app.</p>
162
163<h3 id="unavailable">Make Video Unavailable</h3>
164
165<p>When the user changes the channel, you want to make sure the screen doesn't display any stray
166video artifacts before your TV input renders the content. When you call {@link android.media.tv.TvInputService.Session#onTune(android.net.Uri) TvInputService.Session.onTune()},
167you can prevent the video from being presented by calling {@link android.media.tv.TvInputService.Session#notifyVideoUnavailable(int) TvInputService.Session.notifyVideoUnavailable()}
168and passing the {@link android.media.tv.TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} constant, as
169shown in the following example.</p>
170
171<pre>
172&#64;Override
173public boolean onTune(Uri channelUri) {
174    if (mSubtitleView != null) {
175        mSubtitleView.setVisibility(View.INVISIBLE);
176    }
177    notifyVideoUnavailable(TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
178    mUnblockedRatingSet.clear();
179
180    mDbHandler.removeCallbacks(mPlayCurrentProgramRunnable);
181    mPlayCurrentProgramRunnable = new PlayCurrentProgramRunnable(channelUri);
182    mDbHandler.post(mPlayCurrentProgramRunnable);
183    return true;
184}
185</pre>
186
187<p>Then, when the content is rendered to the {@link android.view.Surface}, you call
188{@link android.media.tv.TvInputService.Session#notifyVideoAvailable() TvInputService.Session.notifyVideoAvailable()}
189to allow the video to display, like so:</p>
190
191<pre>
192&#64;Override
193public void onDrawnToSurface(Surface surface) {
194    mFirstFrameDrawn = true;
195    notifyVideoAvailable();
196}
197</pre>
198
199<p>This transition lasts only for fractions of a second, but presenting a blank screen is
200visually better than allowing the picture to flash odd blips and jitters.</p>
201
202<p>See also, <a href="#surface">Integrate Player with Surface</a> for more information about working
203with {@link android.view.Surface} to render video.</p>
204
205<h3 id="parental">Provide Parental Control</h3>
206
207<p>To determine if a given content is blocked by parental controls and content rating, you check the
208{@link android.media.tv.TvInputManager} class methods, {@link android.media.tv.TvInputManager#isParentalControlsEnabled()}
209and {@link android.media.tv.TvInputManager#isRatingBlocked(android.media.tv.TvContentRating)}. You
210might also want to make sure the content's {@link android.media.tv.TvContentRating} is included in a
211set of currently allowed content ratings. These considerations are shown in the following sample.</p>
212
213<pre>
214private void checkContentBlockNeeded() {
215    if (mCurrentContentRating == null || !mTvInputManager.isParentalControlsEnabled()
216            || !mTvInputManager.isRatingBlocked(mCurrentContentRating)
217            || mUnblockedRatingSet.contains(mCurrentContentRating)) {
218        // Content rating is changed so we don't need to block anymore.
219        // Unblock content here explicitly to resume playback.
220        unblockContent(null);
221        return;
222    }
223
224    mLastBlockedRating = mCurrentContentRating;
225    if (mPlayer != null) {
226        // Children restricted content might be blocked by TV app as well,
227        // but TIF should do its best not to show any single frame of blocked content.
228        releasePlayer();
229    }
230
231    notifyContentBlocked(mCurrentContentRating);
232}
233</pre>
234
235<p>Once you have determined if the content should or should not be blocked, notify the system TV
236app by calling the
237{@link android.media.tv.TvInputService.Session} method {@link android.media.tv.TvInputService.Session#notifyContentAllowed() notifyContentAllowed()}
238or
239{@link android.media.tv.TvInputService.Session#notifyContentBlocked(android.media.tv.TvContentRating) notifyContentBlocked()}
240, as shown in the previous example.</p>
241
242<p>Use the {@link android.media.tv.TvContentRating} class to generate the system-defined string for
243the {@link android.media.tv.TvContract.Programs#COLUMN_CONTENT_RATING} with the
244<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>
245method, as shown here:</p>
246
247<pre>
248TvContentRating rating = TvContentRating.createRating(
249    "com.android.tv",
250    "US_TV",
251    "US_TV_PG",
252    "US_TV_D", "US_TV_L");
253</pre>
254
255<h2 id="track">Handle Track Selection</h2>
256
257<p>The {@link android.media.tv.TvTrackInfo} class holds information about media tracks such
258as the track type (video, audio, or subtitle) and so forth. </p>
259
260<p>The first time your TV input session is able to get track information, it should call
261<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
262is a change in track information, call
263<code><a href="{@docRoot}reference/android/media/tv/TvInputService.Session.html#notifyTracksChanged(java.util.List<android.media.tv.TvTrackInfo>)">notifyTracksChanged()</a></code>
264again to update the system.
265
266</p>
267
268<p>The system TV app provides an interface for the user to select a specific track if more than one
269track is available for a given track type; for example, subtitles in different languages. Your TV
270input responds to the
271{@link android.media.tv.TvInputService.Session#onSelectTrack(int, java.lang.String) onSelectTrack()}
272call from the system TV app by calling
273{@link android.media.tv.TvInputService.Session#notifyTrackSelected(int, java.lang.String) notifyTrackSelected()}
274, as shown in the following example. Note that when <code>null</code>
275is passed as the track ID, this <em>deselects</em> the track.</p>
276
277<pre>
278&#64;Override
279public boolean onSelectTrack(int type, String trackId) {
280    if (mPlayer != null) {
281        if (type == TvTrackInfo.TYPE_SUBTITLE) {
282            if (!mCaptionEnabled && trackId != null) {
283                return false;
284            }
285            mSelectedSubtitleTrackId = trackId;
286            if (trackId == null) {
287                mSubtitleView.setVisibility(View.INVISIBLE);
288            }
289        }
290        if (mPlayer.selectTrack(type, trackId)) {
291            notifyTrackSelected(type, trackId);
292            return true;
293        }
294    }
295    return false;
296}
297</pre>
298
299
300
301
302
303
304
305