• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media.tv;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.graphics.Rect;
25 import android.media.PlaybackParams;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.Looper;
31 import android.os.Message;
32 import android.os.ParcelFileDescriptor;
33 import android.os.RemoteException;
34 import android.text.TextUtils;
35 import android.util.ArrayMap;
36 import android.util.Log;
37 import android.util.Pools.Pool;
38 import android.util.Pools.SimplePool;
39 import android.util.SparseArray;
40 import android.view.InputChannel;
41 import android.view.InputEvent;
42 import android.view.InputEventSender;
43 import android.view.KeyEvent;
44 import android.view.Surface;
45 import android.view.View;
46 
47 import com.android.internal.util.Preconditions;
48 
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 import java.util.ArrayList;
52 import java.util.Iterator;
53 import java.util.LinkedList;
54 import java.util.List;
55 import java.util.Map;
56 
57 /**
58  * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
59  * interaction between applications and the selected TV inputs. You can retrieve an instance of
60  * this interface with {@link android.content.Context#getSystemService
61  * Context.getSystemService(Context.TV_INPUT_SERVICE)}.
62  *
63  * <p>There are three primary parties involved in the TV input framework (TIF) architecture:
64  *
65  * <ul>
66  * <li>The <strong>TV input manager</strong> as expressed by this class is the central point of the
67  * system that manages interaction between all other parts. It is expressed as the client-side API
68  * here which exists in each application context and communicates with a global system service that
69  * manages the interaction across all processes.
70  * <li>A <strong>TV input</strong> implemented by {@link TvInputService} represents an input source
71  * of TV, which can be a pass-through input such as HDMI, or a tuner input which provides broadcast
72  * TV programs. The system binds to the TV input per application’s request.
73  * on implementing TV inputs.
74  * <li><strong>Applications</strong> talk to the TV input manager to list TV inputs and check their
75  * status. Once an application find the input to use, it uses {@link TvView} or
76  * {@link TvRecordingClient} for further interaction such as watching and recording broadcast TV
77  * programs.
78  * </ul>
79  */
80 public final class TvInputManager {
81     private static final String TAG = "TvInputManager";
82 
83     static final int DVB_DEVICE_START = 0;
84     static final int DVB_DEVICE_END = 2;
85 
86     /**
87      * A demux device of DVB API for controlling the filters of DVB hardware/software.
88      * @hide
89      */
90     public static final int DVB_DEVICE_DEMUX = DVB_DEVICE_START;
91      /**
92      * A DVR device of DVB API for reading transport streams.
93      * @hide
94      */
95     public static final int DVB_DEVICE_DVR = 1;
96     /**
97      * A frontend device of DVB API for controlling the tuner and DVB demodulator hardware.
98      * @hide
99      */
100     public static final int DVB_DEVICE_FRONTEND = DVB_DEVICE_END;
101 
102     /** @hide */
103     @Retention(RetentionPolicy.SOURCE)
104     @IntDef({VIDEO_UNAVAILABLE_REASON_UNKNOWN, VIDEO_UNAVAILABLE_REASON_TUNING,
105             VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL, VIDEO_UNAVAILABLE_REASON_BUFFERING,
106             VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY})
107     public @interface VideoUnavailableReason {}
108 
109     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
110     static final int VIDEO_UNAVAILABLE_REASON_END = 4;
111 
112     /**
113      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
114      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable due to
115      * an unspecified error.
116      */
117     public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START;
118     /**
119      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
120      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
121      * the corresponding TV input is in the middle of tuning to a new channel.
122      */
123     public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1;
124     /**
125      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
126      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable due to
127      * weak TV signal.
128      */
129     public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2;
130     /**
131      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
132      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
133      * the corresponding TV input has stopped playback temporarily to buffer more data.
134      */
135     public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
136     /**
137      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
138      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
139      * the current TV program is audio-only.
140      */
141     public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = VIDEO_UNAVAILABLE_REASON_END;
142 
143     /** @hide */
144     @Retention(RetentionPolicy.SOURCE)
145     @IntDef({TIME_SHIFT_STATUS_UNKNOWN, TIME_SHIFT_STATUS_UNSUPPORTED,
146             TIME_SHIFT_STATUS_UNAVAILABLE, TIME_SHIFT_STATUS_AVAILABLE})
147     public @interface TimeShiftStatus {}
148 
149     /**
150      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
151      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: Unknown status. Also
152      * the status prior to calling {@code notifyTimeShiftStatusChanged}.
153      */
154     public static final int TIME_SHIFT_STATUS_UNKNOWN = 0;
155 
156     /**
157      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
158      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: The current TV input
159      * does not support time shifting.
160      */
161     public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1;
162 
163     /**
164      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
165      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: Time shifting is
166      * currently unavailable but might work again later.
167      */
168     public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2;
169 
170     /**
171      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
172      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: Time shifting is
173      * currently available. In this status, the application assumes it can pause/resume playback,
174      * seek to a specified time position and set playback rate and audio mode.
175      */
176     public static final int TIME_SHIFT_STATUS_AVAILABLE = 3;
177 
178     /**
179      * Value returned by {@link TvInputService.Session#onTimeShiftGetCurrentPosition()} and
180      * {@link TvInputService.Session#onTimeShiftGetStartPosition()} when time shifting has not
181      * yet started.
182      */
183     public static final long TIME_SHIFT_INVALID_TIME = Long.MIN_VALUE;
184 
185     /** @hide */
186     @Retention(RetentionPolicy.SOURCE)
187     @IntDef({RECORDING_ERROR_UNKNOWN, RECORDING_ERROR_INSUFFICIENT_SPACE,
188             RECORDING_ERROR_RESOURCE_BUSY})
189     public @interface RecordingError {}
190 
191     static final int RECORDING_ERROR_START = 0;
192     static final int RECORDING_ERROR_END = 2;
193 
194     /**
195      * Error for {@link TvInputService.RecordingSession#notifyError(int)} and
196      * {@link TvRecordingClient.RecordingCallback#onError(int)}: The requested operation cannot be
197      * completed due to a problem that does not fit under any other error codes, or the error code
198      * for the problem is defined on the higher version than application's
199      * <code>android:targetSdkVersion</code>.
200      */
201     public static final int RECORDING_ERROR_UNKNOWN = RECORDING_ERROR_START;
202 
203     /**
204      * Error for {@link TvInputService.RecordingSession#notifyError(int)} and
205      * {@link TvRecordingClient.RecordingCallback#onError(int)}: Recording cannot proceed due to
206      * insufficient storage space.
207      */
208     public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1;
209 
210     /**
211      * Error for {@link TvInputService.RecordingSession#notifyError(int)} and
212      * {@link TvRecordingClient.RecordingCallback#onError(int)}: Recording cannot proceed because
213      * a required recording resource was not able to be allocated.
214      */
215     public static final int RECORDING_ERROR_RESOURCE_BUSY = RECORDING_ERROR_END;
216 
217     /** @hide */
218     @Retention(RetentionPolicy.SOURCE)
219     @IntDef({INPUT_STATE_CONNECTED, INPUT_STATE_CONNECTED_STANDBY, INPUT_STATE_DISCONNECTED})
220     public @interface InputState {}
221 
222     /**
223      * State for {@link #getInputState(String)} and
224      * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is connected.
225      *
226      * <p>This state indicates that a source device is connected to the input port and is in the
227      * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input. This is
228      * the default state for any hardware inputs where their states are unknown. Non-hardware inputs
229      * are considered connected all the time.
230      */
231     public static final int INPUT_STATE_CONNECTED = 0;
232 
233     /**
234      * State for {@link #getInputState(String)} and
235      * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is connected but
236      * in standby mode.
237      *
238      * <p>This state indicates that a source device is connected to the input port but is in standby
239      * mode. It is mostly relevant to hardware inputs such as HDMI input.
240      */
241     public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
242 
243     /**
244      * State for {@link #getInputState(String)} and
245      * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is disconnected.
246      *
247      * <p>This state indicates that a source device is disconnected from the input port. It is
248      * mostly relevant to hardware inputs such as HDMI input.
249      *
250      */
251     public static final int INPUT_STATE_DISCONNECTED = 2;
252 
253     /**
254      * Broadcast intent action when the user blocked content ratings change. For use with the
255      * {@link #isRatingBlocked}.
256      */
257     public static final String ACTION_BLOCKED_RATINGS_CHANGED =
258             "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
259 
260     /**
261      * Broadcast intent action when the parental controls enabled state changes. For use with the
262      * {@link #isParentalControlsEnabled}.
263      */
264     public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED =
265             "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
266 
267     /**
268      * Broadcast intent action used to query available content rating systems.
269      *
270      * <p>The TV input manager service locates available content rating systems by querying
271      * broadcast receivers that are registered for this action. An application can offer additional
272      * content rating systems to the user by declaring a suitable broadcast receiver in its
273      * manifest.
274      *
275      * <p>Here is an example broadcast receiver declaration that an application might include in its
276      * AndroidManifest.xml to advertise custom content rating systems. The meta-data specifies a
277      * resource that contains a description of each content rating system that is provided by the
278      * application.
279      *
280      * <p><pre class="prettyprint">
281      * {@literal
282      * <receiver android:name=".TvInputReceiver">
283      *     <intent-filter>
284      *         <action android:name=
285      *                 "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
286      *     </intent-filter>
287      *     <meta-data
288      *             android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
289      *             android:resource="@xml/tv_content_rating_systems" />
290      * </receiver>}</pre>
291      *
292      * <p>In the above example, the <code>@xml/tv_content_rating_systems</code> resource refers to an
293      * XML resource whose root element is <code>&lt;rating-system-definitions&gt;</code> that
294      * contains zero or more <code>&lt;rating-system-definition&gt;</code> elements. Each <code>
295      * &lt;rating-system-definition&gt;</code> element specifies the ratings, sub-ratings and rating
296      * orders of a particular content rating system.
297      *
298      * @see TvContentRating
299      */
300     public static final String ACTION_QUERY_CONTENT_RATING_SYSTEMS =
301             "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
302 
303     /**
304      * Content rating systems metadata associated with {@link #ACTION_QUERY_CONTENT_RATING_SYSTEMS}.
305      *
306      * <p>Specifies the resource ID of an XML resource that describes the content rating systems
307      * that are provided by the application.
308      */
309     public static final String META_DATA_CONTENT_RATING_SYSTEMS =
310             "android.media.tv.metadata.CONTENT_RATING_SYSTEMS";
311 
312     /**
313      * Activity action to set up channel sources i.e.&nbsp;TV inputs of type
314      * {@link TvInputInfo#TYPE_TUNER}. When invoked, the system will display an appropriate UI for
315      * the user to initiate the individual setup flow provided by
316      * {@link android.R.attr#setupActivity} of each TV input service.
317      */
318     public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
319 
320     private final ITvInputManager mService;
321 
322     private final Object mLock = new Object();
323 
324     // @GuardedBy("mLock")
325     private final List<TvInputCallbackRecord> mCallbackRecords = new LinkedList<>();
326 
327     // A mapping from TV input ID to the state of corresponding input.
328     // @GuardedBy("mLock")
329     private final Map<String, Integer> mStateMap = new ArrayMap<>();
330 
331     // A mapping from the sequence number of a session to its SessionCallbackRecord.
332     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
333             new SparseArray<>();
334 
335     // A sequence number for the next session to be created. Should be protected by a lock
336     // {@code mSessionCallbackRecordMap}.
337     private int mNextSeq;
338 
339     private final ITvInputClient mClient;
340 
341     private final int mUserId;
342 
343     /**
344      * Interface used to receive the created session.
345      * @hide
346      */
347     public abstract static class SessionCallback {
348         /**
349          * This is called after {@link TvInputManager#createSession} has been processed.
350          *
351          * @param session A {@link TvInputManager.Session} instance created. This can be
352          *            {@code null} if the creation request failed.
353          */
onSessionCreated(@ullable Session session)354         public void onSessionCreated(@Nullable Session session) {
355         }
356 
357         /**
358          * This is called when {@link TvInputManager.Session} is released.
359          * This typically happens when the process hosting the session has crashed or been killed.
360          *
361          * @param session A {@link TvInputManager.Session} instance released.
362          */
onSessionReleased(Session session)363         public void onSessionReleased(Session session) {
364         }
365 
366         /**
367          * This is called when the channel of this session is changed by the underlying TV input
368          * without any {@link TvInputManager.Session#tune(Uri)} request.
369          *
370          * @param session A {@link TvInputManager.Session} associated with this callback.
371          * @param channelUri The URI of a channel.
372          */
onChannelRetuned(Session session, Uri channelUri)373         public void onChannelRetuned(Session session, Uri channelUri) {
374         }
375 
376         /**
377          * This is called when the track information of the session has been changed.
378          *
379          * @param session A {@link TvInputManager.Session} associated with this callback.
380          * @param tracks A list which includes track information.
381          */
onTracksChanged(Session session, List<TvTrackInfo> tracks)382         public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
383         }
384 
385         /**
386          * This is called when a track for a given type is selected.
387          *
388          * @param session A {@link TvInputManager.Session} associated with this callback.
389          * @param type The type of the selected track. The type can be
390          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
391          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
392          * @param trackId The ID of the selected track. When {@code null} the currently selected
393          *            track for a given type should be unselected.
394          */
onTrackSelected(Session session, int type, @Nullable String trackId)395         public void onTrackSelected(Session session, int type, @Nullable String trackId) {
396         }
397 
398         /**
399          * This is invoked when the video size has been changed. It is also called when the first
400          * time video size information becomes available after the session is tuned to a specific
401          * channel.
402          *
403          * @param session A {@link TvInputManager.Session} associated with this callback.
404          * @param width The width of the video.
405          * @param height The height of the video.
406          */
onVideoSizeChanged(Session session, int width, int height)407         public void onVideoSizeChanged(Session session, int width, int height) {
408         }
409 
410         /**
411          * This is called when the video is available, so the TV input starts the playback.
412          *
413          * @param session A {@link TvInputManager.Session} associated with this callback.
414          */
onVideoAvailable(Session session)415         public void onVideoAvailable(Session session) {
416         }
417 
418         /**
419          * This is called when the video is not available, so the TV input stops the playback.
420          *
421          * @param session A {@link TvInputManager.Session} associated with this callback.
422          * @param reason The reason why the TV input stopped the playback:
423          * <ul>
424          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
425          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
426          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
427          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
428          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
429          * </ul>
430          */
onVideoUnavailable(Session session, int reason)431         public void onVideoUnavailable(Session session, int reason) {
432         }
433 
434         /**
435          * This is called when the current program content turns out to be allowed to watch since
436          * its content rating is not blocked by parental controls.
437          *
438          * @param session A {@link TvInputManager.Session} associated with this callback.
439          */
onContentAllowed(Session session)440         public void onContentAllowed(Session session) {
441         }
442 
443         /**
444          * This is called when the current program content turns out to be not allowed to watch
445          * since its content rating is blocked by parental controls.
446          *
447          * @param session A {@link TvInputManager.Session} associated with this callback.
448          * @param rating The content ration of the blocked program.
449          */
onContentBlocked(Session session, TvContentRating rating)450         public void onContentBlocked(Session session, TvContentRating rating) {
451         }
452 
453         /**
454          * This is called when {@link TvInputService.Session#layoutSurface} is called to change the
455          * layout of surface.
456          *
457          * @param session A {@link TvInputManager.Session} associated with this callback.
458          * @param left Left position.
459          * @param top Top position.
460          * @param right Right position.
461          * @param bottom Bottom position.
462          */
onLayoutSurface(Session session, int left, int top, int right, int bottom)463         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
464         }
465 
466         /**
467          * This is called when a custom event has been sent from this session.
468          *
469          * @param session A {@link TvInputManager.Session} associated with this callback
470          * @param eventType The type of the event.
471          * @param eventArgs Optional arguments of the event.
472          */
onSessionEvent(Session session, String eventType, Bundle eventArgs)473         public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
474         }
475 
476         /**
477          * This is called when the time shift status is changed.
478          *
479          * @param session A {@link TvInputManager.Session} associated with this callback.
480          * @param status The current time shift status. Should be one of the followings.
481          * <ul>
482          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
483          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
484          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
485          * </ul>
486          */
onTimeShiftStatusChanged(Session session, int status)487         public void onTimeShiftStatusChanged(Session session, int status) {
488         }
489 
490         /**
491          * This is called when the start position for time shifting has changed.
492          *
493          * @param session A {@link TvInputManager.Session} associated with this callback.
494          * @param timeMs The start position for time shifting, in milliseconds since the epoch.
495          */
onTimeShiftStartPositionChanged(Session session, long timeMs)496         public void onTimeShiftStartPositionChanged(Session session, long timeMs) {
497         }
498 
499         /**
500          * This is called when the current position for time shifting is changed.
501          *
502          * @param session A {@link TvInputManager.Session} associated with this callback.
503          * @param timeMs The current position for time shifting, in milliseconds since the epoch.
504          */
onTimeShiftCurrentPositionChanged(Session session, long timeMs)505         public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
506         }
507 
508         // For the recording session only
509         /**
510          * This is called when the recording session has been tuned to the given channel and is
511          * ready to start recording.
512          *
513          * @param channelUri The URI of a channel.
514          */
onTuned(Session session, Uri channelUri)515         void onTuned(Session session, Uri channelUri) {
516         }
517 
518         // For the recording session only
519         /**
520          * This is called when the current recording session has stopped recording and created a
521          * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly
522          * recorded program.
523          *
524          * @param recordedProgramUri The URI for the newly recorded program.
525          **/
onRecordingStopped(Session session, Uri recordedProgramUri)526         void onRecordingStopped(Session session, Uri recordedProgramUri) {
527         }
528 
529         // For the recording session only
530         /**
531          * This is called when an issue has occurred. It may be called at any time after the current
532          * recording session is created until it is released.
533          *
534          * @param error The error code.
535          */
onError(Session session, @TvInputManager.RecordingError int error)536         void onError(Session session, @TvInputManager.RecordingError int error) {
537         }
538     }
539 
540     private static final class SessionCallbackRecord {
541         private final SessionCallback mSessionCallback;
542         private final Handler mHandler;
543         private Session mSession;
544 
SessionCallbackRecord(SessionCallback sessionCallback, Handler handler)545         SessionCallbackRecord(SessionCallback sessionCallback,
546                 Handler handler) {
547             mSessionCallback = sessionCallback;
548             mHandler = handler;
549         }
550 
postSessionCreated(final Session session)551         void postSessionCreated(final Session session) {
552             mSession = session;
553             mHandler.post(new Runnable() {
554                 @Override
555                 public void run() {
556                     mSessionCallback.onSessionCreated(session);
557                 }
558             });
559         }
560 
postSessionReleased()561         void postSessionReleased() {
562             mHandler.post(new Runnable() {
563                 @Override
564                 public void run() {
565                     mSessionCallback.onSessionReleased(mSession);
566                 }
567             });
568         }
569 
postChannelRetuned(final Uri channelUri)570         void postChannelRetuned(final Uri channelUri) {
571             mHandler.post(new Runnable() {
572                 @Override
573                 public void run() {
574                     mSessionCallback.onChannelRetuned(mSession, channelUri);
575                 }
576             });
577         }
578 
postTracksChanged(final List<TvTrackInfo> tracks)579         void postTracksChanged(final List<TvTrackInfo> tracks) {
580             mHandler.post(new Runnable() {
581                 @Override
582                 public void run() {
583                     mSessionCallback.onTracksChanged(mSession, tracks);
584                 }
585             });
586         }
587 
postTrackSelected(final int type, final String trackId)588         void postTrackSelected(final int type, final String trackId) {
589             mHandler.post(new Runnable() {
590                 @Override
591                 public void run() {
592                     mSessionCallback.onTrackSelected(mSession, type, trackId);
593                 }
594             });
595         }
596 
postVideoSizeChanged(final int width, final int height)597         void postVideoSizeChanged(final int width, final int height) {
598             mHandler.post(new Runnable() {
599                 @Override
600                 public void run() {
601                     mSessionCallback.onVideoSizeChanged(mSession, width, height);
602                 }
603             });
604         }
605 
postVideoAvailable()606         void postVideoAvailable() {
607             mHandler.post(new Runnable() {
608                 @Override
609                 public void run() {
610                     mSessionCallback.onVideoAvailable(mSession);
611                 }
612             });
613         }
614 
postVideoUnavailable(final int reason)615         void postVideoUnavailable(final int reason) {
616             mHandler.post(new Runnable() {
617                 @Override
618                 public void run() {
619                     mSessionCallback.onVideoUnavailable(mSession, reason);
620                 }
621             });
622         }
623 
postContentAllowed()624         void postContentAllowed() {
625             mHandler.post(new Runnable() {
626                 @Override
627                 public void run() {
628                     mSessionCallback.onContentAllowed(mSession);
629                 }
630             });
631         }
632 
postContentBlocked(final TvContentRating rating)633         void postContentBlocked(final TvContentRating rating) {
634             mHandler.post(new Runnable() {
635                 @Override
636                 public void run() {
637                     mSessionCallback.onContentBlocked(mSession, rating);
638                 }
639             });
640         }
641 
postLayoutSurface(final int left, final int top, final int right, final int bottom)642         void postLayoutSurface(final int left, final int top, final int right,
643                 final int bottom) {
644             mHandler.post(new Runnable() {
645                 @Override
646                 public void run() {
647                     mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
648                 }
649             });
650         }
651 
postSessionEvent(final String eventType, final Bundle eventArgs)652         void postSessionEvent(final String eventType, final Bundle eventArgs) {
653             mHandler.post(new Runnable() {
654                 @Override
655                 public void run() {
656                     mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
657                 }
658             });
659         }
660 
postTimeShiftStatusChanged(final int status)661         void postTimeShiftStatusChanged(final int status) {
662             mHandler.post(new Runnable() {
663                 @Override
664                 public void run() {
665                     mSessionCallback.onTimeShiftStatusChanged(mSession, status);
666                 }
667             });
668         }
669 
postTimeShiftStartPositionChanged(final long timeMs)670         void postTimeShiftStartPositionChanged(final long timeMs) {
671             mHandler.post(new Runnable() {
672                 @Override
673                 public void run() {
674                     mSessionCallback.onTimeShiftStartPositionChanged(mSession, timeMs);
675                 }
676             });
677         }
678 
postTimeShiftCurrentPositionChanged(final long timeMs)679         void postTimeShiftCurrentPositionChanged(final long timeMs) {
680             mHandler.post(new Runnable() {
681                 @Override
682                 public void run() {
683                     mSessionCallback.onTimeShiftCurrentPositionChanged(mSession, timeMs);
684                 }
685             });
686         }
687 
688         // For the recording session only
postTuned(final Uri channelUri)689         void postTuned(final Uri channelUri) {
690             mHandler.post(new Runnable() {
691                 @Override
692                 public void run() {
693                     mSessionCallback.onTuned(mSession, channelUri);
694                 }
695             });
696         }
697 
698         // For the recording session only
postRecordingStopped(final Uri recordedProgramUri)699         void postRecordingStopped(final Uri recordedProgramUri) {
700             mHandler.post(new Runnable() {
701                 @Override
702                 public void run() {
703                     mSessionCallback.onRecordingStopped(mSession, recordedProgramUri);
704                 }
705             });
706         }
707 
708         // For the recording session only
postError(final int error)709         void postError(final int error) {
710             mHandler.post(new Runnable() {
711                 @Override
712                 public void run() {
713                     mSessionCallback.onError(mSession, error);
714                 }
715             });
716         }
717     }
718 
719     /**
720      * Callback used to monitor status of the TV inputs.
721      */
722     public abstract static class TvInputCallback {
723         /**
724          * This is called when the state of a given TV input is changed.
725          *
726          * @param inputId The ID of the TV input.
727          * @param state State of the TV input. The value is one of the following:
728          * <ul>
729          * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
730          * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
731          * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
732          * </ul>
733          */
onInputStateChanged(String inputId, @InputState int state)734         public void onInputStateChanged(String inputId, @InputState int state) {
735         }
736 
737         /**
738          * This is called when a TV input is added to the system.
739          *
740          * <p>Normally it happens when the user installs a new TV input package that implements
741          * {@link TvInputService} interface.
742          *
743          * @param inputId The ID of the TV input.
744          */
onInputAdded(String inputId)745         public void onInputAdded(String inputId) {
746         }
747 
748         /**
749          * This is called when a TV input is removed from the system.
750          *
751          * <p>Normally it happens when the user uninstalls the previously installed TV input
752          * package.
753          *
754          * @param inputId The ID of the TV input.
755          */
onInputRemoved(String inputId)756         public void onInputRemoved(String inputId) {
757         }
758 
759         /**
760          * This is called when a TV input is updated on the system.
761          *
762          * <p>Normally it happens when a previously installed TV input package is re-installed or
763          * the media on which a newer version of the package exists becomes available/unavailable.
764          *
765          * @param inputId The ID of the TV input.
766          */
onInputUpdated(String inputId)767         public void onInputUpdated(String inputId) {
768         }
769 
770         /**
771          * This is called when the information about an existing TV input has been updated.
772          *
773          * <p>Because the system automatically creates a <code>TvInputInfo</code> object for each TV
774          * input based on the information collected from the <code>AndroidManifest.xml</code>, this
775          * method is only called back when such information has changed dynamically.
776          *
777          * @param inputInfo The <code>TvInputInfo</code> object that contains new information.
778          */
onTvInputInfoUpdated(TvInputInfo inputInfo)779         public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
780         }
781     }
782 
783     private static final class TvInputCallbackRecord {
784         private final TvInputCallback mCallback;
785         private final Handler mHandler;
786 
TvInputCallbackRecord(TvInputCallback callback, Handler handler)787         public TvInputCallbackRecord(TvInputCallback callback, Handler handler) {
788             mCallback = callback;
789             mHandler = handler;
790         }
791 
getCallback()792         public TvInputCallback getCallback() {
793             return mCallback;
794         }
795 
postInputAdded(final String inputId)796         public void postInputAdded(final String inputId) {
797             mHandler.post(new Runnable() {
798                 @Override
799                 public void run() {
800                     mCallback.onInputAdded(inputId);
801                 }
802             });
803         }
804 
postInputRemoved(final String inputId)805         public void postInputRemoved(final String inputId) {
806             mHandler.post(new Runnable() {
807                 @Override
808                 public void run() {
809                     mCallback.onInputRemoved(inputId);
810                 }
811             });
812         }
813 
postInputUpdated(final String inputId)814         public void postInputUpdated(final String inputId) {
815             mHandler.post(new Runnable() {
816                 @Override
817                 public void run() {
818                     mCallback.onInputUpdated(inputId);
819                 }
820             });
821         }
822 
postInputStateChanged(final String inputId, final int state)823         public void postInputStateChanged(final String inputId, final int state) {
824             mHandler.post(new Runnable() {
825                 @Override
826                 public void run() {
827                     mCallback.onInputStateChanged(inputId, state);
828                 }
829             });
830         }
831 
postTvInputInfoUpdated(final TvInputInfo inputInfo)832         public void postTvInputInfoUpdated(final TvInputInfo inputInfo) {
833             mHandler.post(new Runnable() {
834                 @Override
835                 public void run() {
836                     mCallback.onTvInputInfoUpdated(inputInfo);
837                 }
838             });
839         }
840     }
841 
842     /**
843      * Interface used to receive events from Hardware objects.
844      *
845      * @hide
846      */
847     @SystemApi
848     public abstract static class HardwareCallback {
849         /**
850          * This is called when {@link Hardware} is no longer available for the client.
851          */
onReleased()852         public abstract void onReleased();
853 
854         /**
855          * This is called when the underlying {@link TvStreamConfig} has been changed.
856          *
857          * @param configs The new {@link TvStreamConfig}s.
858          */
onStreamConfigChanged(TvStreamConfig[] configs)859         public abstract void onStreamConfigChanged(TvStreamConfig[] configs);
860     }
861 
862     /**
863      * @hide
864      */
TvInputManager(ITvInputManager service, int userId)865     public TvInputManager(ITvInputManager service, int userId) {
866         mService = service;
867         mUserId = userId;
868         mClient = new ITvInputClient.Stub() {
869             @Override
870             public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
871                     int seq) {
872                 synchronized (mSessionCallbackRecordMap) {
873                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
874                     if (record == null) {
875                         Log.e(TAG, "Callback not found for " + token);
876                         return;
877                     }
878                     Session session = null;
879                     if (token != null) {
880                         session = new Session(token, channel, mService, mUserId, seq,
881                                 mSessionCallbackRecordMap);
882                     }
883                     record.postSessionCreated(session);
884                 }
885             }
886 
887             @Override
888             public void onSessionReleased(int seq) {
889                 synchronized (mSessionCallbackRecordMap) {
890                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
891                     mSessionCallbackRecordMap.delete(seq);
892                     if (record == null) {
893                         Log.e(TAG, "Callback not found for seq:" + seq);
894                         return;
895                     }
896                     record.mSession.releaseInternal();
897                     record.postSessionReleased();
898                 }
899             }
900 
901             @Override
902             public void onChannelRetuned(Uri channelUri, int seq) {
903                 synchronized (mSessionCallbackRecordMap) {
904                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
905                     if (record == null) {
906                         Log.e(TAG, "Callback not found for seq " + seq);
907                         return;
908                     }
909                     record.postChannelRetuned(channelUri);
910                 }
911             }
912 
913             @Override
914             public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
915                 synchronized (mSessionCallbackRecordMap) {
916                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
917                     if (record == null) {
918                         Log.e(TAG, "Callback not found for seq " + seq);
919                         return;
920                     }
921                     if (record.mSession.updateTracks(tracks)) {
922                         record.postTracksChanged(tracks);
923                         postVideoSizeChangedIfNeededLocked(record);
924                     }
925                 }
926             }
927 
928             @Override
929             public void onTrackSelected(int type, String trackId, int seq) {
930                 synchronized (mSessionCallbackRecordMap) {
931                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
932                     if (record == null) {
933                         Log.e(TAG, "Callback not found for seq " + seq);
934                         return;
935                     }
936                     if (record.mSession.updateTrackSelection(type, trackId)) {
937                         record.postTrackSelected(type, trackId);
938                         postVideoSizeChangedIfNeededLocked(record);
939                     }
940                 }
941             }
942 
943             private void postVideoSizeChangedIfNeededLocked(SessionCallbackRecord record) {
944                 TvTrackInfo track = record.mSession.getVideoTrackToNotify();
945                 if (track != null) {
946                     record.postVideoSizeChanged(track.getVideoWidth(), track.getVideoHeight());
947                 }
948             }
949 
950             @Override
951             public void onVideoAvailable(int seq) {
952                 synchronized (mSessionCallbackRecordMap) {
953                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
954                     if (record == null) {
955                         Log.e(TAG, "Callback not found for seq " + seq);
956                         return;
957                     }
958                     record.postVideoAvailable();
959                 }
960             }
961 
962             @Override
963             public void onVideoUnavailable(int reason, int seq) {
964                 synchronized (mSessionCallbackRecordMap) {
965                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
966                     if (record == null) {
967                         Log.e(TAG, "Callback not found for seq " + seq);
968                         return;
969                     }
970                     record.postVideoUnavailable(reason);
971                 }
972             }
973 
974             @Override
975             public void onContentAllowed(int seq) {
976                 synchronized (mSessionCallbackRecordMap) {
977                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
978                     if (record == null) {
979                         Log.e(TAG, "Callback not found for seq " + seq);
980                         return;
981                     }
982                     record.postContentAllowed();
983                 }
984             }
985 
986             @Override
987             public void onContentBlocked(String rating, int seq) {
988                 synchronized (mSessionCallbackRecordMap) {
989                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
990                     if (record == null) {
991                         Log.e(TAG, "Callback not found for seq " + seq);
992                         return;
993                     }
994                     record.postContentBlocked(TvContentRating.unflattenFromString(rating));
995                 }
996             }
997 
998             @Override
999             public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
1000                 synchronized (mSessionCallbackRecordMap) {
1001                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1002                     if (record == null) {
1003                         Log.e(TAG, "Callback not found for seq " + seq);
1004                         return;
1005                     }
1006                     record.postLayoutSurface(left, top, right, bottom);
1007                 }
1008             }
1009 
1010             @Override
1011             public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
1012                 synchronized (mSessionCallbackRecordMap) {
1013                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1014                     if (record == null) {
1015                         Log.e(TAG, "Callback not found for seq " + seq);
1016                         return;
1017                     }
1018                     record.postSessionEvent(eventType, eventArgs);
1019                 }
1020             }
1021 
1022             @Override
1023             public void onTimeShiftStatusChanged(int status, int seq) {
1024                 synchronized (mSessionCallbackRecordMap) {
1025                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1026                     if (record == null) {
1027                         Log.e(TAG, "Callback not found for seq " + seq);
1028                         return;
1029                     }
1030                     record.postTimeShiftStatusChanged(status);
1031                 }
1032             }
1033 
1034             @Override
1035             public void onTimeShiftStartPositionChanged(long timeMs, int seq) {
1036                 synchronized (mSessionCallbackRecordMap) {
1037                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1038                     if (record == null) {
1039                         Log.e(TAG, "Callback not found for seq " + seq);
1040                         return;
1041                     }
1042                     record.postTimeShiftStartPositionChanged(timeMs);
1043                 }
1044             }
1045 
1046             @Override
1047             public void onTimeShiftCurrentPositionChanged(long timeMs, int seq) {
1048                 synchronized (mSessionCallbackRecordMap) {
1049                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1050                     if (record == null) {
1051                         Log.e(TAG, "Callback not found for seq " + seq);
1052                         return;
1053                     }
1054                     record.postTimeShiftCurrentPositionChanged(timeMs);
1055                 }
1056             }
1057 
1058             @Override
1059             public void onTuned(int seq, Uri channelUri) {
1060                 synchronized (mSessionCallbackRecordMap) {
1061                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1062                     if (record == null) {
1063                         Log.e(TAG, "Callback not found for seq " + seq);
1064                         return;
1065                     }
1066                     record.postTuned(channelUri);
1067                 }
1068             }
1069 
1070             @Override
1071             public void onRecordingStopped(Uri recordedProgramUri, int seq) {
1072                 synchronized (mSessionCallbackRecordMap) {
1073                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1074                     if (record == null) {
1075                         Log.e(TAG, "Callback not found for seq " + seq);
1076                         return;
1077                     }
1078                     record.postRecordingStopped(recordedProgramUri);
1079                 }
1080             }
1081 
1082             @Override
1083             public void onError(int error, int seq) {
1084                 synchronized (mSessionCallbackRecordMap) {
1085                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1086                     if (record == null) {
1087                         Log.e(TAG, "Callback not found for seq " + seq);
1088                         return;
1089                     }
1090                     record.postError(error);
1091                 }
1092             }
1093         };
1094         ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
1095             @Override
1096             public void onInputAdded(String inputId) {
1097                 synchronized (mLock) {
1098                     mStateMap.put(inputId, INPUT_STATE_CONNECTED);
1099                     for (TvInputCallbackRecord record : mCallbackRecords) {
1100                         record.postInputAdded(inputId);
1101                     }
1102                 }
1103             }
1104 
1105             @Override
1106             public void onInputRemoved(String inputId) {
1107                 synchronized (mLock) {
1108                     mStateMap.remove(inputId);
1109                     for (TvInputCallbackRecord record : mCallbackRecords) {
1110                         record.postInputRemoved(inputId);
1111                     }
1112                 }
1113             }
1114 
1115             @Override
1116             public void onInputUpdated(String inputId) {
1117                 synchronized (mLock) {
1118                     for (TvInputCallbackRecord record : mCallbackRecords) {
1119                         record.postInputUpdated(inputId);
1120                     }
1121                 }
1122             }
1123 
1124             @Override
1125             public void onInputStateChanged(String inputId, int state) {
1126                 synchronized (mLock) {
1127                     mStateMap.put(inputId, state);
1128                     for (TvInputCallbackRecord record : mCallbackRecords) {
1129                         record.postInputStateChanged(inputId, state);
1130                     }
1131                 }
1132             }
1133 
1134             @Override
1135             public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
1136                 synchronized (mLock) {
1137                     for (TvInputCallbackRecord record : mCallbackRecords) {
1138                         record.postTvInputInfoUpdated(inputInfo);
1139                     }
1140                 }
1141             }
1142         };
1143         try {
1144             if (mService != null) {
1145                 mService.registerCallback(managerCallback, mUserId);
1146                 List<TvInputInfo> infos = mService.getTvInputList(mUserId);
1147                 synchronized (mLock) {
1148                     for (TvInputInfo info : infos) {
1149                         String inputId = info.getId();
1150                         mStateMap.put(inputId, mService.getTvInputState(inputId, mUserId));
1151                     }
1152                 }
1153             }
1154         } catch (RemoteException e) {
1155             throw e.rethrowFromSystemServer();
1156         }
1157     }
1158 
1159     /**
1160      * Returns the complete list of TV inputs on the system.
1161      *
1162      * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
1163      */
getTvInputList()1164     public List<TvInputInfo> getTvInputList() {
1165         try {
1166             return mService.getTvInputList(mUserId);
1167         } catch (RemoteException e) {
1168             throw e.rethrowFromSystemServer();
1169         }
1170     }
1171 
1172     /**
1173      * Returns the {@link TvInputInfo} for a given TV input.
1174      *
1175      * @param inputId The ID of the TV input.
1176      * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
1177      */
1178     @Nullable
getTvInputInfo(@onNull String inputId)1179     public TvInputInfo getTvInputInfo(@NonNull String inputId) {
1180         Preconditions.checkNotNull(inputId);
1181         try {
1182             return mService.getTvInputInfo(inputId, mUserId);
1183         } catch (RemoteException e) {
1184             throw e.rethrowFromSystemServer();
1185         }
1186     }
1187 
1188     /**
1189      * Updates the <code>TvInputInfo</code> for an existing TV input. A TV input service
1190      * implementation may call this method to pass the application and system an up-to-date
1191      * <code>TvInputInfo</code> object that describes itself.
1192      *
1193      * <p>The system automatically creates a <code>TvInputInfo</code> object for each TV input,
1194      * based on the information collected from the <code>AndroidManifest.xml</code>, thus it is not
1195      * necessary to call this method unless such information has changed dynamically.
1196      * Use {@link TvInputInfo.Builder} to build a new <code>TvInputInfo</code> object.
1197      *
1198      * <p>Attempting to change information about a TV input that the calling package does not own
1199      * does nothing.
1200      *
1201      * @param inputInfo The <code>TvInputInfo</code> object that contains new information.
1202      * @throws IllegalArgumentException if the argument is {@code null}.
1203      * @see TvInputCallback#onTvInputInfoUpdated(TvInputInfo)
1204      */
updateTvInputInfo(@onNull TvInputInfo inputInfo)1205     public void updateTvInputInfo(@NonNull TvInputInfo inputInfo) {
1206         Preconditions.checkNotNull(inputInfo);
1207         try {
1208             mService.updateTvInputInfo(inputInfo, mUserId);
1209         } catch (RemoteException e) {
1210             throw e.rethrowFromSystemServer();
1211         }
1212     }
1213 
1214     /**
1215      * Returns the state of a given TV input.
1216      *
1217      * <p>The state is one of the following:
1218      * <ul>
1219      * <li>{@link #INPUT_STATE_CONNECTED}
1220      * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
1221      * <li>{@link #INPUT_STATE_DISCONNECTED}
1222      * </ul>
1223      *
1224      * @param inputId The ID of the TV input.
1225      * @throws IllegalArgumentException if the argument is {@code null}.
1226      */
1227     @InputState
getInputState(@onNull String inputId)1228     public int getInputState(@NonNull String inputId) {
1229         Preconditions.checkNotNull(inputId);
1230         synchronized (mLock) {
1231             Integer state = mStateMap.get(inputId);
1232             if (state == null) {
1233                 Log.w(TAG, "Unrecognized input ID: " + inputId);
1234                 return INPUT_STATE_DISCONNECTED;
1235             }
1236             return state;
1237         }
1238     }
1239 
1240     /**
1241      * Registers a {@link TvInputCallback}.
1242      *
1243      * @param callback A callback used to monitor status of the TV inputs.
1244      * @param handler A {@link Handler} that the status change will be delivered to.
1245      */
registerCallback(@onNull TvInputCallback callback, @NonNull Handler handler)1246     public void registerCallback(@NonNull TvInputCallback callback, @NonNull Handler handler) {
1247         Preconditions.checkNotNull(callback);
1248         Preconditions.checkNotNull(handler);
1249         synchronized (mLock) {
1250             mCallbackRecords.add(new TvInputCallbackRecord(callback, handler));
1251         }
1252     }
1253 
1254     /**
1255      * Unregisters the existing {@link TvInputCallback}.
1256      *
1257      * @param callback The existing callback to remove.
1258      */
unregisterCallback(@onNull final TvInputCallback callback)1259     public void unregisterCallback(@NonNull final TvInputCallback callback) {
1260         Preconditions.checkNotNull(callback);
1261         synchronized (mLock) {
1262             for (Iterator<TvInputCallbackRecord> it = mCallbackRecords.iterator();
1263                     it.hasNext(); ) {
1264                 TvInputCallbackRecord record = it.next();
1265                 if (record.getCallback() == callback) {
1266                     it.remove();
1267                     break;
1268                 }
1269             }
1270         }
1271     }
1272 
1273     /**
1274      * Returns the user's parental controls enabled state.
1275      *
1276      * @return {@code true} if the user enabled the parental controls, {@code false} otherwise.
1277      */
isParentalControlsEnabled()1278     public boolean isParentalControlsEnabled() {
1279         try {
1280             return mService.isParentalControlsEnabled(mUserId);
1281         } catch (RemoteException e) {
1282             throw e.rethrowFromSystemServer();
1283         }
1284     }
1285 
1286     /**
1287      * Sets the user's parental controls enabled state.
1288      *
1289      * @param enabled The user's parental controls enabled state. {@code true} if the user enabled
1290      *            the parental controls, {@code false} otherwise.
1291      * @see #isParentalControlsEnabled
1292      * @hide
1293      */
1294     @SystemApi
1295     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
setParentalControlsEnabled(boolean enabled)1296     public void setParentalControlsEnabled(boolean enabled) {
1297         try {
1298             mService.setParentalControlsEnabled(enabled, mUserId);
1299         } catch (RemoteException e) {
1300             throw e.rethrowFromSystemServer();
1301         }
1302     }
1303 
1304     /**
1305      * Checks whether a given TV content rating is blocked by the user.
1306      *
1307      * @param rating The TV content rating to check. Can be {@link TvContentRating#UNRATED}.
1308      * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
1309      */
isRatingBlocked(@onNull TvContentRating rating)1310     public boolean isRatingBlocked(@NonNull TvContentRating rating) {
1311         Preconditions.checkNotNull(rating);
1312         try {
1313             return mService.isRatingBlocked(rating.flattenToString(), mUserId);
1314         } catch (RemoteException e) {
1315             throw e.rethrowFromSystemServer();
1316         }
1317     }
1318 
1319     /**
1320      * Returns the list of blocked content ratings.
1321      *
1322      * @return the list of content ratings blocked by the user.
1323      * @hide
1324      */
1325     @SystemApi
getBlockedRatings()1326     public List<TvContentRating> getBlockedRatings() {
1327         try {
1328             List<TvContentRating> ratings = new ArrayList<>();
1329             for (String rating : mService.getBlockedRatings(mUserId)) {
1330                 ratings.add(TvContentRating.unflattenFromString(rating));
1331             }
1332             return ratings;
1333         } catch (RemoteException e) {
1334             throw e.rethrowFromSystemServer();
1335         }
1336     }
1337 
1338     /**
1339      * Adds a user blocked content rating.
1340      *
1341      * @param rating The content rating to block.
1342      * @see #isRatingBlocked
1343      * @see #removeBlockedRating
1344      * @hide
1345      */
1346     @SystemApi
1347     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
addBlockedRating(@onNull TvContentRating rating)1348     public void addBlockedRating(@NonNull TvContentRating rating) {
1349         Preconditions.checkNotNull(rating);
1350         try {
1351             mService.addBlockedRating(rating.flattenToString(), mUserId);
1352         } catch (RemoteException e) {
1353             throw e.rethrowFromSystemServer();
1354         }
1355     }
1356 
1357     /**
1358      * Removes a user blocked content rating.
1359      *
1360      * @param rating The content rating to unblock.
1361      * @see #isRatingBlocked
1362      * @see #addBlockedRating
1363      * @hide
1364      */
1365     @SystemApi
1366     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
removeBlockedRating(@onNull TvContentRating rating)1367     public void removeBlockedRating(@NonNull TvContentRating rating) {
1368         Preconditions.checkNotNull(rating);
1369         try {
1370             mService.removeBlockedRating(rating.flattenToString(), mUserId);
1371         } catch (RemoteException e) {
1372             throw e.rethrowFromSystemServer();
1373         }
1374     }
1375 
1376     /**
1377      * Returns the list of all TV content rating systems defined.
1378      * @hide
1379      */
1380     @SystemApi
getTvContentRatingSystemList()1381     public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() {
1382         try {
1383             return mService.getTvContentRatingSystemList(mUserId);
1384         } catch (RemoteException e) {
1385             throw e.rethrowFromSystemServer();
1386         }
1387     }
1388 
1389     /**
1390      * Creates a {@link Session} for a given TV input.
1391      *
1392      * <p>The number of sessions that can be created at the same time is limited by the capability
1393      * of the given TV input.
1394      *
1395      * @param inputId The ID of the TV input.
1396      * @param callback A callback used to receive the created session.
1397      * @param handler A {@link Handler} that the session creation will be delivered to.
1398      * @hide
1399      */
createSession(@onNull String inputId, @NonNull final SessionCallback callback, @NonNull Handler handler)1400     public void createSession(@NonNull String inputId, @NonNull final SessionCallback callback,
1401             @NonNull Handler handler) {
1402         createSessionInternal(inputId, false, callback, handler);
1403     }
1404 
1405     /**
1406      * Creates a recording {@link Session} for a given TV input.
1407      *
1408      * <p>The number of sessions that can be created at the same time is limited by the capability
1409      * of the given TV input.
1410      *
1411      * @param inputId The ID of the TV input.
1412      * @param callback A callback used to receive the created session.
1413      * @param handler A {@link Handler} that the session creation will be delivered to.
1414      * @hide
1415      */
createRecordingSession(@onNull String inputId, @NonNull final SessionCallback callback, @NonNull Handler handler)1416     public void createRecordingSession(@NonNull String inputId,
1417             @NonNull final SessionCallback callback, @NonNull Handler handler) {
1418         createSessionInternal(inputId, true, callback, handler);
1419     }
1420 
createSessionInternal(String inputId, boolean isRecordingSession, SessionCallback callback, Handler handler)1421     private void createSessionInternal(String inputId, boolean isRecordingSession,
1422             SessionCallback callback, Handler handler) {
1423         Preconditions.checkNotNull(inputId);
1424         Preconditions.checkNotNull(callback);
1425         Preconditions.checkNotNull(handler);
1426         SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
1427         synchronized (mSessionCallbackRecordMap) {
1428             int seq = mNextSeq++;
1429             mSessionCallbackRecordMap.put(seq, record);
1430             try {
1431                 mService.createSession(mClient, inputId, isRecordingSession, seq, mUserId);
1432             } catch (RemoteException e) {
1433                 throw e.rethrowFromSystemServer();
1434             }
1435         }
1436     }
1437 
1438     /**
1439      * Returns the TvStreamConfig list of the given TV input.
1440      *
1441      * If you are using {@link Hardware} object from {@link
1442      * #acquireTvInputHardware}, you should get the list of available streams
1443      * from {@link HardwareCallback#onStreamConfigChanged} method, not from
1444      * here. This method is designed to be used with {@link #captureFrame} in
1445      * capture scenarios specifically and not suitable for any other use.
1446      *
1447      * @param inputId The ID of the TV input.
1448      * @return List of {@link TvStreamConfig} which is available for capturing
1449      *   of the given TV input.
1450      * @hide
1451      */
1452     @SystemApi
getAvailableTvStreamConfigList(String inputId)1453     public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId) {
1454         try {
1455             return mService.getAvailableTvStreamConfigList(inputId, mUserId);
1456         } catch (RemoteException e) {
1457             throw e.rethrowFromSystemServer();
1458         }
1459     }
1460 
1461     /**
1462      * Take a snapshot of the given TV input into the provided Surface.
1463      *
1464      * @param inputId The ID of the TV input.
1465      * @param surface the {@link Surface} to which the snapshot is captured.
1466      * @param config the {@link TvStreamConfig} which is used for capturing.
1467      * @return true when the {@link Surface} is ready to be captured.
1468      * @hide
1469      */
1470     @SystemApi
captureFrame(String inputId, Surface surface, TvStreamConfig config)1471     public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) {
1472         try {
1473             return mService.captureFrame(inputId, surface, config, mUserId);
1474         } catch (RemoteException e) {
1475             throw e.rethrowFromSystemServer();
1476         }
1477     }
1478 
1479     /**
1480      * Returns true if there is only a single TV input session.
1481      *
1482      * @hide
1483      */
1484     @SystemApi
isSingleSessionActive()1485     public boolean isSingleSessionActive() {
1486         try {
1487             return mService.isSingleSessionActive(mUserId);
1488         } catch (RemoteException e) {
1489             throw e.rethrowFromSystemServer();
1490         }
1491     }
1492 
1493     /**
1494      * Returns a list of TvInputHardwareInfo objects representing available hardware.
1495      *
1496      * @hide
1497      */
1498     @SystemApi
1499     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
getHardwareList()1500     public List<TvInputHardwareInfo> getHardwareList() {
1501         try {
1502             return mService.getHardwareList();
1503         } catch (RemoteException e) {
1504             throw e.rethrowFromSystemServer();
1505         }
1506     }
1507 
1508     /**
1509      * Acquires {@link Hardware} object for the given device ID.
1510      *
1511      * <p>A subsequent call to this method on the same {@code deviceId} will release the currently
1512      * acquired Hardware.
1513      *
1514      * @param deviceId The device ID to acquire Hardware for.
1515      * @param callback A callback to receive updates on Hardware.
1516      * @param info The TV input which will use the acquired Hardware.
1517      * @return Hardware on success, {@code null} otherwise.
1518      *
1519      * @removed
1520      */
1521     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
acquireTvInputHardware(int deviceId, final HardwareCallback callback, TvInputInfo info)1522     public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback,
1523             TvInputInfo info) {
1524         return acquireTvInputHardware(deviceId, info, callback);
1525     }
1526 
1527     /**
1528      * Acquires {@link Hardware} object for the given device ID.
1529      *
1530      * <p>A subsequent call to this method on the same {@code deviceId} will release the currently
1531      * acquired Hardware.
1532      *
1533      * @param deviceId The device ID to acquire Hardware for.
1534      * @param callback A callback to receive updates on Hardware.
1535      * @param info The TV input which will use the acquired Hardware.
1536      * @return Hardware on success, {@code null} otherwise.
1537      *
1538      * @hide
1539      */
1540     @SystemApi
1541     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
acquireTvInputHardware(int deviceId, TvInputInfo info, final HardwareCallback callback)1542     public Hardware acquireTvInputHardware(int deviceId, TvInputInfo info,
1543             final HardwareCallback callback) {
1544         try {
1545             return new Hardware(
1546                     mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() {
1547                 @Override
1548                 public void onReleased() {
1549                     callback.onReleased();
1550                 }
1551 
1552                 @Override
1553                 public void onStreamConfigChanged(TvStreamConfig[] configs) {
1554                     callback.onStreamConfigChanged(configs);
1555                 }
1556             }, info, mUserId));
1557         } catch (RemoteException e) {
1558             throw e.rethrowFromSystemServer();
1559         }
1560     }
1561 
1562     /**
1563      * Releases previously acquired hardware object.
1564      *
1565      * @param deviceId The device ID this Hardware was acquired for
1566      * @param hardware Hardware to release.
1567      *
1568      * @hide
1569      */
1570     @SystemApi
1571     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1572     public void releaseTvInputHardware(int deviceId, Hardware hardware) {
1573         try {
1574             mService.releaseTvInputHardware(deviceId, hardware.getInterface(), mUserId);
1575         } catch (RemoteException e) {
1576             throw e.rethrowFromSystemServer();
1577         }
1578     }
1579 
1580     /**
1581      * Returns the list of currently available DVB devices on the system.
1582      *
1583      * @return the list of {@link DvbDeviceInfo} objects representing available DVB devices.
1584      * @hide
1585      */
1586     public List<DvbDeviceInfo> getDvbDeviceList() {
1587         try {
1588             return mService.getDvbDeviceList();
1589         } catch (RemoteException e) {
1590             throw e.rethrowFromSystemServer();
1591         }
1592     }
1593 
1594     /**
1595      * Returns a {@link ParcelFileDescriptor} of a specified DVB device for a given
1596      * {@link DvbDeviceInfo}
1597      *
1598      * @param info A {@link DvbDeviceInfo} to open a DVB device.
1599      * @param device A DVB device. The DVB device can be {@link #DVB_DEVICE_DEMUX},
1600      *            {@link #DVB_DEVICE_DVR} or {@link #DVB_DEVICE_FRONTEND}.
1601      * @return a {@link ParcelFileDescriptor} of a specified DVB device for a given
1602      *         {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo} was invalid
1603      *         or the specified DVB device was busy with a previous request.
1604      * @hide
1605      */
1606     public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device) {
1607         try {
1608             if (DVB_DEVICE_START > device || DVB_DEVICE_END < device) {
1609                 throw new IllegalArgumentException("Invalid DVB device: " + device);
1610             }
1611             return mService.openDvbDevice(info, device);
1612         } catch (RemoteException e) {
1613             throw e.rethrowFromSystemServer();
1614         }
1615     }
1616 
1617     /**
1618      * The Session provides the per-session functionality of TV inputs.
1619      * @hide
1620      */
1621     public static final class Session {
1622         static final int DISPATCH_IN_PROGRESS = -1;
1623         static final int DISPATCH_NOT_HANDLED = 0;
1624         static final int DISPATCH_HANDLED = 1;
1625 
1626         private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
1627 
1628         private final ITvInputManager mService;
1629         private final int mUserId;
1630         private final int mSeq;
1631 
1632         // For scheduling input event handling on the main thread. This also serves as a lock to
1633         // protect pending input events and the input channel.
1634         private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
1635 
1636         private final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
1637         private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
1638         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
1639 
1640         private IBinder mToken;
1641         private TvInputEventSender mSender;
1642         private InputChannel mChannel;
1643 
1644         private final Object mMetadataLock = new Object();
1645         // @GuardedBy("mMetadataLock")
1646         private final List<TvTrackInfo> mAudioTracks = new ArrayList<>();
1647         // @GuardedBy("mMetadataLock")
1648         private final List<TvTrackInfo> mVideoTracks = new ArrayList<>();
1649         // @GuardedBy("mMetadataLock")
1650         private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<>();
1651         // @GuardedBy("mMetadataLock")
1652         private String mSelectedAudioTrackId;
1653         // @GuardedBy("mMetadataLock")
1654         private String mSelectedVideoTrackId;
1655         // @GuardedBy("mMetadataLock")
1656         private String mSelectedSubtitleTrackId;
1657         // @GuardedBy("mMetadataLock")
1658         private int mVideoWidth;
1659         // @GuardedBy("mMetadataLock")
1660         private int mVideoHeight;
1661 
1662         private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
1663                 int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
1664             mToken = token;
1665             mChannel = channel;
1666             mService = service;
1667             mUserId = userId;
1668             mSeq = seq;
1669             mSessionCallbackRecordMap = sessionCallbackRecordMap;
1670         }
1671 
1672         /**
1673          * Releases this session.
1674          */
1675         public void release() {
1676             if (mToken == null) {
1677                 Log.w(TAG, "The session has been already released");
1678                 return;
1679             }
1680             try {
1681                 mService.releaseSession(mToken, mUserId);
1682             } catch (RemoteException e) {
1683                 throw e.rethrowFromSystemServer();
1684             }
1685 
1686             releaseInternal();
1687         }
1688 
1689         /**
1690          * Sets this as the main session. The main session is a session whose corresponding TV
1691          * input determines the HDMI-CEC active source device.
1692          *
1693          * @see TvView#setMain
1694          */
1695         void setMain() {
1696             if (mToken == null) {
1697                 Log.w(TAG, "The session has been already released");
1698                 return;
1699             }
1700             try {
1701                 mService.setMainSession(mToken, mUserId);
1702             } catch (RemoteException e) {
1703                 throw e.rethrowFromSystemServer();
1704             }
1705         }
1706 
1707         /**
1708          * Sets the {@link android.view.Surface} for this session.
1709          *
1710          * @param surface A {@link android.view.Surface} used to render video.
1711          */
1712         public void setSurface(Surface surface) {
1713             if (mToken == null) {
1714                 Log.w(TAG, "The session has been already released");
1715                 return;
1716             }
1717             // surface can be null.
1718             try {
1719                 mService.setSurface(mToken, surface, mUserId);
1720             } catch (RemoteException e) {
1721                 throw e.rethrowFromSystemServer();
1722             }
1723         }
1724 
1725         /**
1726          * Notifies of any structural changes (format or size) of the surface passed in
1727          * {@link #setSurface}.
1728          *
1729          * @param format The new PixelFormat of the surface.
1730          * @param width The new width of the surface.
1731          * @param height The new height of the surface.
1732          */
1733         public void dispatchSurfaceChanged(int format, int width, int height) {
1734             if (mToken == null) {
1735                 Log.w(TAG, "The session has been already released");
1736                 return;
1737             }
1738             try {
1739                 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
1740             } catch (RemoteException e) {
1741                 throw e.rethrowFromSystemServer();
1742             }
1743         }
1744 
1745         /**
1746          * Sets the relative stream volume of this session to handle a change of audio focus.
1747          *
1748          * @param volume A volume value between 0.0f to 1.0f.
1749          * @throws IllegalArgumentException if the volume value is out of range.
1750          */
1751         public void setStreamVolume(float volume) {
1752             if (mToken == null) {
1753                 Log.w(TAG, "The session has been already released");
1754                 return;
1755             }
1756             try {
1757                 if (volume < 0.0f || volume > 1.0f) {
1758                     throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
1759                 }
1760                 mService.setVolume(mToken, volume, mUserId);
1761             } catch (RemoteException e) {
1762                 throw e.rethrowFromSystemServer();
1763             }
1764         }
1765 
1766         /**
1767          * Tunes to a given channel.
1768          *
1769          * @param channelUri The URI of a channel.
1770          */
1771         public void tune(Uri channelUri) {
1772             tune(channelUri, null);
1773         }
1774 
1775         /**
1776          * Tunes to a given channel.
1777          *
1778          * @param channelUri The URI of a channel.
1779          * @param params A set of extra parameters which might be handled with this tune event.
1780          */
1781         public void tune(@NonNull Uri channelUri, Bundle params) {
1782             Preconditions.checkNotNull(channelUri);
1783             if (mToken == null) {
1784                 Log.w(TAG, "The session has been already released");
1785                 return;
1786             }
1787             synchronized (mMetadataLock) {
1788                 mAudioTracks.clear();
1789                 mVideoTracks.clear();
1790                 mSubtitleTracks.clear();
1791                 mSelectedAudioTrackId = null;
1792                 mSelectedVideoTrackId = null;
1793                 mSelectedSubtitleTrackId = null;
1794                 mVideoWidth = 0;
1795                 mVideoHeight = 0;
1796             }
1797             try {
1798                 mService.tune(mToken, channelUri, params, mUserId);
1799             } catch (RemoteException e) {
1800                 throw e.rethrowFromSystemServer();
1801             }
1802         }
1803 
1804         /**
1805          * Enables or disables the caption for this session.
1806          *
1807          * @param enabled {@code true} to enable, {@code false} to disable.
1808          */
1809         public void setCaptionEnabled(boolean enabled) {
1810             if (mToken == null) {
1811                 Log.w(TAG, "The session has been already released");
1812                 return;
1813             }
1814             try {
1815                 mService.setCaptionEnabled(mToken, enabled, mUserId);
1816             } catch (RemoteException e) {
1817                 throw e.rethrowFromSystemServer();
1818             }
1819         }
1820 
1821         /**
1822          * Selects a track.
1823          *
1824          * @param type The type of the track to select. The type can be
1825          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
1826          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
1827          * @param trackId The ID of the track to select. When {@code null}, the currently selected
1828          *            track of the given type will be unselected.
1829          * @see #getTracks
1830          */
1831         public void selectTrack(int type, @Nullable String trackId) {
1832             synchronized (mMetadataLock) {
1833                 if (type == TvTrackInfo.TYPE_AUDIO) {
1834                     if (trackId != null && !containsTrack(mAudioTracks, trackId)) {
1835                         Log.w(TAG, "Invalid audio trackId: " + trackId);
1836                         return;
1837                     }
1838                 } else if (type == TvTrackInfo.TYPE_VIDEO) {
1839                     if (trackId != null && !containsTrack(mVideoTracks, trackId)) {
1840                         Log.w(TAG, "Invalid video trackId: " + trackId);
1841                         return;
1842                     }
1843                 } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1844                     if (trackId != null && !containsTrack(mSubtitleTracks, trackId)) {
1845                         Log.w(TAG, "Invalid subtitle trackId: " + trackId);
1846                         return;
1847                     }
1848                 } else {
1849                     throw new IllegalArgumentException("invalid type: " + type);
1850                 }
1851             }
1852             if (mToken == null) {
1853                 Log.w(TAG, "The session has been already released");
1854                 return;
1855             }
1856             try {
1857                 mService.selectTrack(mToken, type, trackId, mUserId);
1858             } catch (RemoteException e) {
1859                 throw e.rethrowFromSystemServer();
1860             }
1861         }
1862 
1863         private boolean containsTrack(List<TvTrackInfo> tracks, String trackId) {
1864             for (TvTrackInfo track : tracks) {
1865                 if (track.getId().equals(trackId)) {
1866                     return true;
1867                 }
1868             }
1869             return false;
1870         }
1871 
1872         /**
1873          * Returns the list of tracks for a given type. Returns {@code null} if the information is
1874          * not available.
1875          *
1876          * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
1877          *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
1878          * @return the list of tracks for the given type.
1879          */
1880         @Nullable
1881         public List<TvTrackInfo> getTracks(int type) {
1882             synchronized (mMetadataLock) {
1883                 if (type == TvTrackInfo.TYPE_AUDIO) {
1884                     if (mAudioTracks == null) {
1885                         return null;
1886                     }
1887                     return new ArrayList<>(mAudioTracks);
1888                 } else if (type == TvTrackInfo.TYPE_VIDEO) {
1889                     if (mVideoTracks == null) {
1890                         return null;
1891                     }
1892                     return new ArrayList<>(mVideoTracks);
1893                 } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1894                     if (mSubtitleTracks == null) {
1895                         return null;
1896                     }
1897                     return new ArrayList<>(mSubtitleTracks);
1898                 }
1899             }
1900             throw new IllegalArgumentException("invalid type: " + type);
1901         }
1902 
1903         /**
1904          * Returns the selected track for a given type. Returns {@code null} if the information is
1905          * not available or any of the tracks for the given type is not selected.
1906          *
1907          * @return The ID of the selected track.
1908          * @see #selectTrack
1909          */
1910         @Nullable
1911         public String getSelectedTrack(int type) {
1912             synchronized (mMetadataLock) {
1913                 if (type == TvTrackInfo.TYPE_AUDIO) {
1914                     return mSelectedAudioTrackId;
1915                 } else if (type == TvTrackInfo.TYPE_VIDEO) {
1916                     return mSelectedVideoTrackId;
1917                 } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1918                     return mSelectedSubtitleTrackId;
1919                 }
1920             }
1921             throw new IllegalArgumentException("invalid type: " + type);
1922         }
1923 
1924         /**
1925          * Responds to onTracksChanged() and updates the internal track information. Returns true if
1926          * there is an update.
1927          */
1928         boolean updateTracks(List<TvTrackInfo> tracks) {
1929             synchronized (mMetadataLock) {
1930                 mAudioTracks.clear();
1931                 mVideoTracks.clear();
1932                 mSubtitleTracks.clear();
1933                 for (TvTrackInfo track : tracks) {
1934                     if (track.getType() == TvTrackInfo.TYPE_AUDIO) {
1935                         mAudioTracks.add(track);
1936                     } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
1937                         mVideoTracks.add(track);
1938                     } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
1939                         mSubtitleTracks.add(track);
1940                     }
1941                 }
1942                 return !mAudioTracks.isEmpty() || !mVideoTracks.isEmpty()
1943                         || !mSubtitleTracks.isEmpty();
1944             }
1945         }
1946 
1947         /**
1948          * Responds to onTrackSelected() and updates the internal track selection information.
1949          * Returns true if there is an update.
1950          */
1951         boolean updateTrackSelection(int type, String trackId) {
1952             synchronized (mMetadataLock) {
1953                 if (type == TvTrackInfo.TYPE_AUDIO
1954                         && !TextUtils.equals(trackId, mSelectedAudioTrackId)) {
1955                     mSelectedAudioTrackId = trackId;
1956                     return true;
1957                 } else if (type == TvTrackInfo.TYPE_VIDEO
1958                         && !TextUtils.equals(trackId, mSelectedVideoTrackId)) {
1959                     mSelectedVideoTrackId = trackId;
1960                     return true;
1961                 } else if (type == TvTrackInfo.TYPE_SUBTITLE
1962                         && !TextUtils.equals(trackId, mSelectedSubtitleTrackId)) {
1963                     mSelectedSubtitleTrackId = trackId;
1964                     return true;
1965                 }
1966             }
1967             return false;
1968         }
1969 
1970         /**
1971          * Returns the new/updated video track that contains new video size information. Returns
1972          * null if there is no video track to notify. Subsequent calls of this method results in a
1973          * non-null video track returned only by the first call and null returned by following
1974          * calls. The caller should immediately notify of the video size change upon receiving the
1975          * track.
1976          */
1977         TvTrackInfo getVideoTrackToNotify() {
1978             synchronized (mMetadataLock) {
1979                 if (!mVideoTracks.isEmpty() && mSelectedVideoTrackId != null) {
1980                     for (TvTrackInfo track : mVideoTracks) {
1981                         if (track.getId().equals(mSelectedVideoTrackId)) {
1982                             int videoWidth = track.getVideoWidth();
1983                             int videoHeight = track.getVideoHeight();
1984                             if (mVideoWidth != videoWidth || mVideoHeight != videoHeight) {
1985                                 mVideoWidth = videoWidth;
1986                                 mVideoHeight = videoHeight;
1987                                 return track;
1988                             }
1989                         }
1990                     }
1991                 }
1992             }
1993             return null;
1994         }
1995 
1996         /**
1997          * Plays a given recorded TV program.
1998          */
1999         void timeShiftPlay(Uri recordedProgramUri) {
2000             if (mToken == null) {
2001                 Log.w(TAG, "The session has been already released");
2002                 return;
2003             }
2004             try {
2005                 mService.timeShiftPlay(mToken, recordedProgramUri, mUserId);
2006             } catch (RemoteException e) {
2007                 throw e.rethrowFromSystemServer();
2008             }
2009         }
2010 
2011         /**
2012          * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback.
2013          */
2014         void timeShiftPause() {
2015             if (mToken == null) {
2016                 Log.w(TAG, "The session has been already released");
2017                 return;
2018             }
2019             try {
2020                 mService.timeShiftPause(mToken, mUserId);
2021             } catch (RemoteException e) {
2022                 throw e.rethrowFromSystemServer();
2023             }
2024         }
2025 
2026         /**
2027          * Resumes the playback. No-op if it is already playing the channel.
2028          */
2029         void timeShiftResume() {
2030             if (mToken == null) {
2031                 Log.w(TAG, "The session has been already released");
2032                 return;
2033             }
2034             try {
2035                 mService.timeShiftResume(mToken, mUserId);
2036             } catch (RemoteException e) {
2037                 throw e.rethrowFromSystemServer();
2038             }
2039         }
2040 
2041         /**
2042          * Seeks to a specified time position.
2043          *
2044          * <p>Normally, the position is given within range between the start and the current time,
2045          * inclusively.
2046          *
2047          * @param timeMs The time position to seek to, in milliseconds since the epoch.
2048          * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged
2049          */
2050         void timeShiftSeekTo(long timeMs) {
2051             if (mToken == null) {
2052                 Log.w(TAG, "The session has been already released");
2053                 return;
2054             }
2055             try {
2056                 mService.timeShiftSeekTo(mToken, timeMs, mUserId);
2057             } catch (RemoteException e) {
2058                 throw e.rethrowFromSystemServer();
2059             }
2060         }
2061 
2062         /**
2063          * Sets playback rate using {@link android.media.PlaybackParams}.
2064          *
2065          * @param params The playback params.
2066          */
2067         void timeShiftSetPlaybackParams(PlaybackParams params) {
2068             if (mToken == null) {
2069                 Log.w(TAG, "The session has been already released");
2070                 return;
2071             }
2072             try {
2073                 mService.timeShiftSetPlaybackParams(mToken, params, mUserId);
2074             } catch (RemoteException e) {
2075                 throw e.rethrowFromSystemServer();
2076             }
2077         }
2078 
2079         /**
2080          * Enable/disable position tracking.
2081          *
2082          * @param enable {@code true} to enable tracking, {@code false} otherwise.
2083          */
2084         void timeShiftEnablePositionTracking(boolean enable) {
2085             if (mToken == null) {
2086                 Log.w(TAG, "The session has been already released");
2087                 return;
2088             }
2089             try {
2090                 mService.timeShiftEnablePositionTracking(mToken, enable, mUserId);
2091             } catch (RemoteException e) {
2092                 throw e.rethrowFromSystemServer();
2093             }
2094         }
2095 
2096         /**
2097          * Starts TV program recording in the current recording session.
2098          *
2099          * @param programUri The URI for the TV program to record as a hint, built by
2100          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
2101          */
2102         void startRecording(@Nullable Uri programUri) {
2103             if (mToken == null) {
2104                 Log.w(TAG, "The session has been already released");
2105                 return;
2106             }
2107             try {
2108                 mService.startRecording(mToken, programUri, mUserId);
2109             } catch (RemoteException e) {
2110                 throw e.rethrowFromSystemServer();
2111             }
2112         }
2113 
2114         /**
2115          * Stops TV program recording in the current recording session.
2116          */
2117         void stopRecording() {
2118             if (mToken == null) {
2119                 Log.w(TAG, "The session has been already released");
2120                 return;
2121             }
2122             try {
2123                 mService.stopRecording(mToken, mUserId);
2124             } catch (RemoteException e) {
2125                 throw e.rethrowFromSystemServer();
2126             }
2127         }
2128 
2129         /**
2130          * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)
2131          * TvInputService.Session.appPrivateCommand()} on the current TvView.
2132          *
2133          * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
2134          *            i.e. prefixed with a package name you own, so that different developers will
2135          *            not create conflicting commands.
2136          * @param data Any data to include with the command.
2137          */
2138         public void sendAppPrivateCommand(String action, Bundle data) {
2139             if (mToken == null) {
2140                 Log.w(TAG, "The session has been already released");
2141                 return;
2142             }
2143             try {
2144                 mService.sendAppPrivateCommand(mToken, action, data, mUserId);
2145             } catch (RemoteException e) {
2146                 throw e.rethrowFromSystemServer();
2147             }
2148         }
2149 
2150         /**
2151          * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
2152          * should be called whenever the layout of its containing view is changed.
2153          * {@link #removeOverlayView()} should be called to remove the overlay view.
2154          * Since a session can have only one overlay view, this method should be called only once
2155          * or it can be called again after calling {@link #removeOverlayView()}.
2156          *
2157          * @param view A view playing TV.
2158          * @param frame A position of the overlay view.
2159          * @throws IllegalStateException if {@code view} is not attached to a window.
2160          */
2161         void createOverlayView(@NonNull View view, @NonNull Rect frame) {
2162             Preconditions.checkNotNull(view);
2163             Preconditions.checkNotNull(frame);
2164             if (view.getWindowToken() == null) {
2165                 throw new IllegalStateException("view must be attached to a window");
2166             }
2167             if (mToken == null) {
2168                 Log.w(TAG, "The session has been already released");
2169                 return;
2170             }
2171             try {
2172                 mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
2173             } catch (RemoteException e) {
2174                 throw e.rethrowFromSystemServer();
2175             }
2176         }
2177 
2178         /**
2179          * Relayouts the current overlay view.
2180          *
2181          * @param frame A new position of the overlay view.
2182          */
2183         void relayoutOverlayView(@NonNull Rect frame) {
2184             Preconditions.checkNotNull(frame);
2185             if (mToken == null) {
2186                 Log.w(TAG, "The session has been already released");
2187                 return;
2188             }
2189             try {
2190                 mService.relayoutOverlayView(mToken, frame, mUserId);
2191             } catch (RemoteException e) {
2192                 throw e.rethrowFromSystemServer();
2193             }
2194         }
2195 
2196         /**
2197          * Removes the current overlay view.
2198          */
2199         void removeOverlayView() {
2200             if (mToken == null) {
2201                 Log.w(TAG, "The session has been already released");
2202                 return;
2203             }
2204             try {
2205                 mService.removeOverlayView(mToken, mUserId);
2206             } catch (RemoteException e) {
2207                 throw e.rethrowFromSystemServer();
2208             }
2209         }
2210 
2211         /**
2212          * Requests to unblock content blocked by parental controls.
2213          */
2214         void unblockContent(@NonNull TvContentRating unblockedRating) {
2215             Preconditions.checkNotNull(unblockedRating);
2216             if (mToken == null) {
2217                 Log.w(TAG, "The session has been already released");
2218                 return;
2219             }
2220             try {
2221                 mService.unblockContent(mToken, unblockedRating.flattenToString(), mUserId);
2222             } catch (RemoteException e) {
2223                 throw e.rethrowFromSystemServer();
2224             }
2225         }
2226 
2227         /**
2228          * Dispatches an input event to this session.
2229          *
2230          * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
2231          * @param token A token used to identify the input event later in the callback.
2232          * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
2233          * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
2234          *            {@code null}.
2235          * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
2236          *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
2237          *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
2238          *         be invoked later.
2239          * @hide
2240          */
2241         public int dispatchInputEvent(@NonNull InputEvent event, Object token,
2242                 @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
2243             Preconditions.checkNotNull(event);
2244             Preconditions.checkNotNull(callback);
2245             Preconditions.checkNotNull(handler);
2246             synchronized (mHandler) {
2247                 if (mChannel == null) {
2248                     return DISPATCH_NOT_HANDLED;
2249                 }
2250                 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
2251                 if (Looper.myLooper() == Looper.getMainLooper()) {
2252                     // Already running on the main thread so we can send the event immediately.
2253                     return sendInputEventOnMainLooperLocked(p);
2254                 }
2255 
2256                 // Post the event to the main thread.
2257                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
2258                 msg.setAsynchronous(true);
2259                 mHandler.sendMessage(msg);
2260                 return DISPATCH_IN_PROGRESS;
2261             }
2262         }
2263 
2264         /**
2265          * Callback that is invoked when an input event that was dispatched to this session has been
2266          * finished.
2267          *
2268          * @hide
2269          */
2270         public interface FinishedInputEventCallback {
2271             /**
2272              * Called when the dispatched input event is finished.
2273              *
2274              * @param token A token passed to {@link #dispatchInputEvent}.
2275              * @param handled {@code true} if the dispatched input event was handled properly.
2276              *            {@code false} otherwise.
2277              */
2278             void onFinishedInputEvent(Object token, boolean handled);
2279         }
2280 
2281         // Must be called on the main looper
2282         private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
2283             synchronized (mHandler) {
2284                 int result = sendInputEventOnMainLooperLocked(p);
2285                 if (result == DISPATCH_IN_PROGRESS) {
2286                     return;
2287                 }
2288             }
2289 
2290             invokeFinishedInputEventCallback(p, false);
2291         }
2292 
2293         private int sendInputEventOnMainLooperLocked(PendingEvent p) {
2294             if (mChannel != null) {
2295                 if (mSender == null) {
2296                     mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
2297                 }
2298 
2299                 final InputEvent event = p.mEvent;
2300                 final int seq = event.getSequenceNumber();
2301                 if (mSender.sendInputEvent(seq, event)) {
2302                     mPendingEvents.put(seq, p);
2303                     Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
2304                     msg.setAsynchronous(true);
2305                     mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
2306                     return DISPATCH_IN_PROGRESS;
2307                 }
2308 
2309                 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
2310                         + event);
2311             }
2312             return DISPATCH_NOT_HANDLED;
2313         }
2314 
2315         void finishedInputEvent(int seq, boolean handled, boolean timeout) {
2316             final PendingEvent p;
2317             synchronized (mHandler) {
2318                 int index = mPendingEvents.indexOfKey(seq);
2319                 if (index < 0) {
2320                     return; // spurious, event already finished or timed out
2321                 }
2322 
2323                 p = mPendingEvents.valueAt(index);
2324                 mPendingEvents.removeAt(index);
2325 
2326                 if (timeout) {
2327                     Log.w(TAG, "Timeout waiting for session to handle input event after "
2328                             + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
2329                 } else {
2330                     mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
2331                 }
2332             }
2333 
2334             invokeFinishedInputEventCallback(p, handled);
2335         }
2336 
2337         // Assumes the event has already been removed from the queue.
2338         void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
2339             p.mHandled = handled;
2340             if (p.mEventHandler.getLooper().isCurrentThread()) {
2341                 // Already running on the callback handler thread so we can send the callback
2342                 // immediately.
2343                 p.run();
2344             } else {
2345                 // Post the event to the callback handler thread.
2346                 // In this case, the callback will be responsible for recycling the event.
2347                 Message msg = Message.obtain(p.mEventHandler, p);
2348                 msg.setAsynchronous(true);
2349                 msg.sendToTarget();
2350             }
2351         }
2352 
2353         private void flushPendingEventsLocked() {
2354             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
2355 
2356             final int count = mPendingEvents.size();
2357             for (int i = 0; i < count; i++) {
2358                 int seq = mPendingEvents.keyAt(i);
2359                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
2360                 msg.setAsynchronous(true);
2361                 msg.sendToTarget();
2362             }
2363         }
2364 
2365         private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
2366                 FinishedInputEventCallback callback, Handler handler) {
2367             PendingEvent p = mPendingEventPool.acquire();
2368             if (p == null) {
2369                 p = new PendingEvent();
2370             }
2371             p.mEvent = event;
2372             p.mEventToken = token;
2373             p.mCallback = callback;
2374             p.mEventHandler = handler;
2375             return p;
2376         }
2377 
2378         private void recyclePendingEventLocked(PendingEvent p) {
2379             p.recycle();
2380             mPendingEventPool.release(p);
2381         }
2382 
2383         IBinder getToken() {
2384             return mToken;
2385         }
2386 
2387         private void releaseInternal() {
2388             mToken = null;
2389             synchronized (mHandler) {
2390                 if (mChannel != null) {
2391                     if (mSender != null) {
2392                         flushPendingEventsLocked();
2393                         mSender.dispose();
2394                         mSender = null;
2395                     }
2396                     mChannel.dispose();
2397                     mChannel = null;
2398                 }
2399             }
2400             synchronized (mSessionCallbackRecordMap) {
2401                 mSessionCallbackRecordMap.remove(mSeq);
2402             }
2403         }
2404 
2405         private final class InputEventHandler extends Handler {
2406             public static final int MSG_SEND_INPUT_EVENT = 1;
2407             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
2408             public static final int MSG_FLUSH_INPUT_EVENT = 3;
2409 
2410             InputEventHandler(Looper looper) {
2411                 super(looper, null, true);
2412             }
2413 
2414             @Override
2415             public void handleMessage(Message msg) {
2416                 switch (msg.what) {
2417                     case MSG_SEND_INPUT_EVENT: {
2418                         sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
2419                         return;
2420                     }
2421                     case MSG_TIMEOUT_INPUT_EVENT: {
2422                         finishedInputEvent(msg.arg1, false, true);
2423                         return;
2424                     }
2425                     case MSG_FLUSH_INPUT_EVENT: {
2426                         finishedInputEvent(msg.arg1, false, false);
2427                         return;
2428                     }
2429                 }
2430             }
2431         }
2432 
2433         private final class TvInputEventSender extends InputEventSender {
2434             public TvInputEventSender(InputChannel inputChannel, Looper looper) {
2435                 super(inputChannel, looper);
2436             }
2437 
2438             @Override
2439             public void onInputEventFinished(int seq, boolean handled) {
2440                 finishedInputEvent(seq, handled, false);
2441             }
2442         }
2443 
2444         private final class PendingEvent implements Runnable {
2445             public InputEvent mEvent;
2446             public Object mEventToken;
2447             public FinishedInputEventCallback mCallback;
2448             public Handler mEventHandler;
2449             public boolean mHandled;
2450 
2451             public void recycle() {
2452                 mEvent = null;
2453                 mEventToken = null;
2454                 mCallback = null;
2455                 mEventHandler = null;
2456                 mHandled = false;
2457             }
2458 
2459             @Override
2460             public void run() {
2461                 mCallback.onFinishedInputEvent(mEventToken, mHandled);
2462 
2463                 synchronized (mEventHandler) {
2464                     recyclePendingEventLocked(this);
2465                 }
2466             }
2467         }
2468     }
2469 
2470     /**
2471      * The Hardware provides the per-hardware functionality of TV hardware.
2472      *
2473      * <p>TV hardware is physical hardware attached to the Android device; for example, HDMI ports,
2474      * Component/Composite ports, etc. Specifically, logical devices such as HDMI CEC logical
2475      * devices don't fall into this category.
2476      *
2477      * @hide
2478      */
2479     @SystemApi
2480     public final static class Hardware {
2481         private final ITvInputHardware mInterface;
2482 
2483         private Hardware(ITvInputHardware hardwareInterface) {
2484             mInterface = hardwareInterface;
2485         }
2486 
2487         private ITvInputHardware getInterface() {
2488             return mInterface;
2489         }
2490 
2491         public boolean setSurface(Surface surface, TvStreamConfig config) {
2492             try {
2493                 return mInterface.setSurface(surface, config);
2494             } catch (RemoteException e) {
2495                 throw new RuntimeException(e);
2496             }
2497         }
2498 
2499         public void setStreamVolume(float volume) {
2500             try {
2501                 mInterface.setStreamVolume(volume);
2502             } catch (RemoteException e) {
2503                 throw new RuntimeException(e);
2504             }
2505         }
2506 
2507         public boolean dispatchKeyEventToHdmi(KeyEvent event) {
2508             try {
2509                 return mInterface.dispatchKeyEventToHdmi(event);
2510             } catch (RemoteException e) {
2511                 throw new RuntimeException(e);
2512             }
2513         }
2514 
2515         public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
2516                 int channelMask, int format) {
2517             try {
2518                 mInterface.overrideAudioSink(audioType, audioAddress, samplingRate, channelMask,
2519                         format);
2520             } catch (RemoteException e) {
2521                 throw new RuntimeException(e);
2522             }
2523         }
2524     }
2525 }
2526