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