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