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