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