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