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