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