TvInputManager.java revision 5557bef5e47fb7a70cd2be03384863ad9f9c5a14
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 playback position is changed.
487         *
488         * <p>The start playback position of the time shifted program should be adjusted when the TV
489         * input cannot retain the whole recorded program due to some reason (e.g. limitation on
490         * storage space). This is necessary to prevent the application from allowing the user to
491         * seek to a time position that is not reachable.
492         *
493         * @param session A {@link TvInputManager.Session} associated with this callback.
494         * @param timeMs The start playback position of the time shifted program, in milliseconds
495         *            since the epoch.
496         */
497        public void onTimeShiftStartPositionChanged(Session session, long timeMs) {
498        }
499
500        /**
501         * This is called when the current playback position is changed.
502         *
503         * @param session A {@link TvInputManager.Session} associated with this callback.
504         * @param timeMs The current playback position of the time shifted program, in milliseconds
505         *            since the epoch.
506         */
507        public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
508        }
509
510        // For the recording session only
511        /**
512         * This is called when the recording session has been tuned to the given channel and is
513         * ready to start recording.
514         *
515         * @param channelUri The URI of a channel.
516         */
517        void onTuned(Session session, Uri channelUri) {
518        }
519
520        // For the recording session only
521        /**
522         * This is called when the current recording session has stopped recording and created a
523         * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly
524         * recorded program.
525         *
526         * @param recordedProgramUri The URI for the newly recorded program.
527         **/
528        void onRecordingStopped(Session session, Uri recordedProgramUri) {
529        }
530
531        // For the recording session only
532        /**
533         * This is called when an issue has occurred. It may be called at any time after the current
534         * recording session is created until it is released.
535         *
536         * @param error The error code.
537         */
538        void onError(Session session, @TvInputManager.RecordingError int error) {
539        }
540    }
541
542    private static final class SessionCallbackRecord {
543        private final SessionCallback mSessionCallback;
544        private final Handler mHandler;
545        private Session mSession;
546
547        SessionCallbackRecord(SessionCallback sessionCallback,
548                Handler handler) {
549            mSessionCallback = sessionCallback;
550            mHandler = handler;
551        }
552
553        void postSessionCreated(final Session session) {
554            mSession = session;
555            mHandler.post(new Runnable() {
556                @Override
557                public void run() {
558                    mSessionCallback.onSessionCreated(session);
559                }
560            });
561        }
562
563        void postSessionReleased() {
564            mHandler.post(new Runnable() {
565                @Override
566                public void run() {
567                    mSessionCallback.onSessionReleased(mSession);
568                }
569            });
570        }
571
572        void postChannelRetuned(final Uri channelUri) {
573            mHandler.post(new Runnable() {
574                @Override
575                public void run() {
576                    mSessionCallback.onChannelRetuned(mSession, channelUri);
577                }
578            });
579        }
580
581        void postTracksChanged(final List<TvTrackInfo> tracks) {
582            mHandler.post(new Runnable() {
583                @Override
584                public void run() {
585                    mSessionCallback.onTracksChanged(mSession, tracks);
586                }
587            });
588        }
589
590        void postTrackSelected(final int type, final String trackId) {
591            mHandler.post(new Runnable() {
592                @Override
593                public void run() {
594                    mSessionCallback.onTrackSelected(mSession, type, trackId);
595                }
596            });
597        }
598
599        void postVideoSizeChanged(final int width, final int height) {
600            mHandler.post(new Runnable() {
601                @Override
602                public void run() {
603                    mSessionCallback.onVideoSizeChanged(mSession, width, height);
604                }
605            });
606        }
607
608        void postVideoAvailable() {
609            mHandler.post(new Runnable() {
610                @Override
611                public void run() {
612                    mSessionCallback.onVideoAvailable(mSession);
613                }
614            });
615        }
616
617        void postVideoUnavailable(final int reason) {
618            mHandler.post(new Runnable() {
619                @Override
620                public void run() {
621                    mSessionCallback.onVideoUnavailable(mSession, reason);
622                }
623            });
624        }
625
626        void postContentAllowed() {
627            mHandler.post(new Runnable() {
628                @Override
629                public void run() {
630                    mSessionCallback.onContentAllowed(mSession);
631                }
632            });
633        }
634
635        void postContentBlocked(final TvContentRating rating) {
636            mHandler.post(new Runnable() {
637                @Override
638                public void run() {
639                    mSessionCallback.onContentBlocked(mSession, rating);
640                }
641            });
642        }
643
644        void postLayoutSurface(final int left, final int top, final int right,
645                final int bottom) {
646            mHandler.post(new Runnable() {
647                @Override
648                public void run() {
649                    mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
650                }
651            });
652        }
653
654        void postSessionEvent(final String eventType, final Bundle eventArgs) {
655            mHandler.post(new Runnable() {
656                @Override
657                public void run() {
658                    mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
659                }
660            });
661        }
662
663        void postTimeShiftStatusChanged(final int status) {
664            mHandler.post(new Runnable() {
665                @Override
666                public void run() {
667                    mSessionCallback.onTimeShiftStatusChanged(mSession, status);
668                }
669            });
670        }
671
672        void postTimeShiftStartPositionChanged(final long timeMs) {
673            mHandler.post(new Runnable() {
674                @Override
675                public void run() {
676                    mSessionCallback.onTimeShiftStartPositionChanged(mSession, timeMs);
677                }
678            });
679        }
680
681        void postTimeShiftCurrentPositionChanged(final long timeMs) {
682            mHandler.post(new Runnable() {
683                @Override
684                public void run() {
685                    mSessionCallback.onTimeShiftCurrentPositionChanged(mSession, timeMs);
686                }
687            });
688        }
689
690        // For the recording session only
691        void postTuned(final Uri channelUri) {
692            mHandler.post(new Runnable() {
693                @Override
694                public void run() {
695                    mSessionCallback.onTuned(mSession, channelUri);
696                }
697            });
698        }
699
700        // For the recording session only
701        void postRecordingStopped(final Uri recordedProgramUri) {
702            mHandler.post(new Runnable() {
703                @Override
704                public void run() {
705                    mSessionCallback.onRecordingStopped(mSession, recordedProgramUri);
706                }
707            });
708        }
709
710        // For the recording session only
711        void postError(final int error) {
712            mHandler.post(new Runnable() {
713                @Override
714                public void run() {
715                    mSessionCallback.onError(mSession, error);
716                }
717            });
718        }
719    }
720
721    /**
722     * Callback used to monitor status of the TV inputs.
723     */
724    public abstract static class TvInputCallback {
725        /**
726         * This is called when the state of a given TV input is changed.
727         *
728         * @param inputId The ID of the TV input.
729         * @param state State of the TV input. The value is one of the following:
730         * <ul>
731         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
732         * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
733         * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
734         * </ul>
735         */
736        public void onInputStateChanged(String inputId, @InputState int state) {
737        }
738
739        /**
740         * This is called when a TV input is added to the system.
741         *
742         * <p>Normally it happens when the user installs a new TV input package that implements
743         * {@link TvInputService} interface.
744         *
745         * @param inputId The ID of the TV input.
746         */
747        public void onInputAdded(String inputId) {
748        }
749
750        /**
751         * This is called when a TV input is removed from the system.
752         *
753         * <p>Normally it happens when the user uninstalls the previously installed TV input
754         * package.
755         *
756         * @param inputId The ID of the TV input.
757         */
758        public void onInputRemoved(String inputId) {
759        }
760
761        /**
762         * This is called when a TV input is updated on the system.
763         *
764         * <p>Normally it happens when a previously installed TV input package is re-installed or
765         * the media on which a newer version of the package exists becomes available/unavailable.
766         *
767         * @param inputId The ID of the TV input.
768         */
769        public void onInputUpdated(String inputId) {
770        }
771
772        /**
773         * This is called when the information about an existing TV input has been updated.
774         *
775         * <p>Because the system automatically creates a <code>TvInputInfo</code> object for each TV
776         * input based on the information collected from the <code>AndroidManifest.xml</code>, this
777         * method is only called back when such information has changed dynamically.
778         *
779         * @param inputInfo The <code>TvInputInfo</code> object that contains new information.
780         */
781        public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
782        }
783    }
784
785    private static final class TvInputCallbackRecord {
786        private final TvInputCallback mCallback;
787        private final Handler mHandler;
788
789        public TvInputCallbackRecord(TvInputCallback callback, Handler handler) {
790            mCallback = callback;
791            mHandler = handler;
792        }
793
794        public TvInputCallback getCallback() {
795            return mCallback;
796        }
797
798        public void postInputAdded(final String inputId) {
799            mHandler.post(new Runnable() {
800                @Override
801                public void run() {
802                    mCallback.onInputAdded(inputId);
803                }
804            });
805        }
806
807        public void postInputRemoved(final String inputId) {
808            mHandler.post(new Runnable() {
809                @Override
810                public void run() {
811                    mCallback.onInputRemoved(inputId);
812                }
813            });
814        }
815
816        public void postInputUpdated(final String inputId) {
817            mHandler.post(new Runnable() {
818                @Override
819                public void run() {
820                    mCallback.onInputUpdated(inputId);
821                }
822            });
823        }
824
825        public void postInputStateChanged(final String inputId, final int state) {
826            mHandler.post(new Runnable() {
827                @Override
828                public void run() {
829                    mCallback.onInputStateChanged(inputId, state);
830                }
831            });
832        }
833
834        public void postTvInputInfoUpdated(final TvInputInfo inputInfo) {
835            mHandler.post(new Runnable() {
836                @Override
837                public void run() {
838                    mCallback.onTvInputInfoUpdated(inputInfo);
839                }
840            });
841        }
842    }
843
844    /**
845     * Interface used to receive events from Hardware objects.
846     * @hide
847     */
848    @SystemApi
849    public abstract static class HardwareCallback {
850        public abstract void onReleased();
851        public abstract void onStreamConfigChanged(TvStreamConfig[] configs);
852    }
853
854    /**
855     * @hide
856     */
857    public TvInputManager(ITvInputManager service, int userId) {
858        mService = service;
859        mUserId = userId;
860        mClient = new ITvInputClient.Stub() {
861            @Override
862            public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
863                    int seq) {
864                synchronized (mSessionCallbackRecordMap) {
865                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
866                    if (record == null) {
867                        Log.e(TAG, "Callback not found for " + token);
868                        return;
869                    }
870                    Session session = null;
871                    if (token != null) {
872                        session = new Session(token, channel, mService, mUserId, seq,
873                                mSessionCallbackRecordMap);
874                    }
875                    record.postSessionCreated(session);
876                }
877            }
878
879            @Override
880            public void onSessionReleased(int seq) {
881                synchronized (mSessionCallbackRecordMap) {
882                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
883                    mSessionCallbackRecordMap.delete(seq);
884                    if (record == null) {
885                        Log.e(TAG, "Callback not found for seq:" + seq);
886                        return;
887                    }
888                    record.mSession.releaseInternal();
889                    record.postSessionReleased();
890                }
891            }
892
893            @Override
894            public void onChannelRetuned(Uri channelUri, int seq) {
895                synchronized (mSessionCallbackRecordMap) {
896                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
897                    if (record == null) {
898                        Log.e(TAG, "Callback not found for seq " + seq);
899                        return;
900                    }
901                    record.postChannelRetuned(channelUri);
902                }
903            }
904
905            @Override
906            public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
907                synchronized (mSessionCallbackRecordMap) {
908                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
909                    if (record == null) {
910                        Log.e(TAG, "Callback not found for seq " + seq);
911                        return;
912                    }
913                    if (record.mSession.updateTracks(tracks)) {
914                        record.postTracksChanged(tracks);
915                        postVideoSizeChangedIfNeededLocked(record);
916                    }
917                }
918            }
919
920            @Override
921            public void onTrackSelected(int type, String trackId, int seq) {
922                synchronized (mSessionCallbackRecordMap) {
923                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
924                    if (record == null) {
925                        Log.e(TAG, "Callback not found for seq " + seq);
926                        return;
927                    }
928                    if (record.mSession.updateTrackSelection(type, trackId)) {
929                        record.postTrackSelected(type, trackId);
930                        postVideoSizeChangedIfNeededLocked(record);
931                    }
932                }
933            }
934
935            private void postVideoSizeChangedIfNeededLocked(SessionCallbackRecord record) {
936                TvTrackInfo track = record.mSession.getVideoTrackToNotify();
937                if (track != null) {
938                    record.postVideoSizeChanged(track.getVideoWidth(), track.getVideoHeight());
939                }
940            }
941
942            @Override
943            public void onVideoAvailable(int seq) {
944                synchronized (mSessionCallbackRecordMap) {
945                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
946                    if (record == null) {
947                        Log.e(TAG, "Callback not found for seq " + seq);
948                        return;
949                    }
950                    record.postVideoAvailable();
951                }
952            }
953
954            @Override
955            public void onVideoUnavailable(int reason, int seq) {
956                synchronized (mSessionCallbackRecordMap) {
957                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
958                    if (record == null) {
959                        Log.e(TAG, "Callback not found for seq " + seq);
960                        return;
961                    }
962                    record.postVideoUnavailable(reason);
963                }
964            }
965
966            @Override
967            public void onContentAllowed(int seq) {
968                synchronized (mSessionCallbackRecordMap) {
969                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
970                    if (record == null) {
971                        Log.e(TAG, "Callback not found for seq " + seq);
972                        return;
973                    }
974                    record.postContentAllowed();
975                }
976            }
977
978            @Override
979            public void onContentBlocked(String rating, int seq) {
980                synchronized (mSessionCallbackRecordMap) {
981                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
982                    if (record == null) {
983                        Log.e(TAG, "Callback not found for seq " + seq);
984                        return;
985                    }
986                    record.postContentBlocked(TvContentRating.unflattenFromString(rating));
987                }
988            }
989
990            @Override
991            public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
992                synchronized (mSessionCallbackRecordMap) {
993                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
994                    if (record == null) {
995                        Log.e(TAG, "Callback not found for seq " + seq);
996                        return;
997                    }
998                    record.postLayoutSurface(left, top, right, bottom);
999                }
1000            }
1001
1002            @Override
1003            public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
1004                synchronized (mSessionCallbackRecordMap) {
1005                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1006                    if (record == null) {
1007                        Log.e(TAG, "Callback not found for seq " + seq);
1008                        return;
1009                    }
1010                    record.postSessionEvent(eventType, eventArgs);
1011                }
1012            }
1013
1014            @Override
1015            public void onTimeShiftStatusChanged(int status, int seq) {
1016                synchronized (mSessionCallbackRecordMap) {
1017                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1018                    if (record == null) {
1019                        Log.e(TAG, "Callback not found for seq " + seq);
1020                        return;
1021                    }
1022                    record.postTimeShiftStatusChanged(status);
1023                }
1024            }
1025
1026            @Override
1027            public void onTimeShiftStartPositionChanged(long timeMs, int seq) {
1028                synchronized (mSessionCallbackRecordMap) {
1029                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1030                    if (record == null) {
1031                        Log.e(TAG, "Callback not found for seq " + seq);
1032                        return;
1033                    }
1034                    record.postTimeShiftStartPositionChanged(timeMs);
1035                }
1036            }
1037
1038            @Override
1039            public void onTimeShiftCurrentPositionChanged(long timeMs, int seq) {
1040                synchronized (mSessionCallbackRecordMap) {
1041                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1042                    if (record == null) {
1043                        Log.e(TAG, "Callback not found for seq " + seq);
1044                        return;
1045                    }
1046                    record.postTimeShiftCurrentPositionChanged(timeMs);
1047                }
1048            }
1049
1050            @Override
1051            public void onTuned(int seq, Uri channelUri) {
1052                synchronized (mSessionCallbackRecordMap) {
1053                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1054                    if (record == null) {
1055                        Log.e(TAG, "Callback not found for seq " + seq);
1056                        return;
1057                    }
1058                    record.postTuned(channelUri);
1059                }
1060            }
1061
1062            @Override
1063            public void onRecordingStopped(Uri recordedProgramUri, int seq) {
1064                synchronized (mSessionCallbackRecordMap) {
1065                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1066                    if (record == null) {
1067                        Log.e(TAG, "Callback not found for seq " + seq);
1068                        return;
1069                    }
1070                    record.postRecordingStopped(recordedProgramUri);
1071                }
1072            }
1073
1074            @Override
1075            public void onError(int error, int seq) {
1076                synchronized (mSessionCallbackRecordMap) {
1077                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1078                    if (record == null) {
1079                        Log.e(TAG, "Callback not found for seq " + seq);
1080                        return;
1081                    }
1082                    record.postError(error);
1083                }
1084            }
1085        };
1086        ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
1087            @Override
1088            public void onInputAdded(String inputId) {
1089                synchronized (mLock) {
1090                    mStateMap.put(inputId, INPUT_STATE_CONNECTED);
1091                    for (TvInputCallbackRecord record : mCallbackRecords) {
1092                        record.postInputAdded(inputId);
1093                    }
1094                }
1095            }
1096
1097            @Override
1098            public void onInputRemoved(String inputId) {
1099                synchronized (mLock) {
1100                    mStateMap.remove(inputId);
1101                    for (TvInputCallbackRecord record : mCallbackRecords) {
1102                        record.postInputRemoved(inputId);
1103                    }
1104                }
1105            }
1106
1107            @Override
1108            public void onInputUpdated(String inputId) {
1109                synchronized (mLock) {
1110                    for (TvInputCallbackRecord record : mCallbackRecords) {
1111                        record.postInputUpdated(inputId);
1112                    }
1113                }
1114            }
1115
1116            @Override
1117            public void onInputStateChanged(String inputId, int state) {
1118                synchronized (mLock) {
1119                    mStateMap.put(inputId, state);
1120                    for (TvInputCallbackRecord record : mCallbackRecords) {
1121                        record.postInputStateChanged(inputId, state);
1122                    }
1123                }
1124            }
1125
1126            @Override
1127            public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
1128                synchronized (mLock) {
1129                    for (TvInputCallbackRecord record : mCallbackRecords) {
1130                        record.postTvInputInfoUpdated(inputInfo);
1131                    }
1132                }
1133            }
1134        };
1135        try {
1136            if (mService != null) {
1137                mService.registerCallback(managerCallback, mUserId);
1138                List<TvInputInfo> infos = mService.getTvInputList(mUserId);
1139                synchronized (mLock) {
1140                    for (TvInputInfo info : infos) {
1141                        String inputId = info.getId();
1142                        mStateMap.put(inputId, mService.getTvInputState(inputId, mUserId));
1143                    }
1144                }
1145            }
1146        } catch (RemoteException e) {
1147            throw e.rethrowFromSystemServer();
1148        }
1149    }
1150
1151    /**
1152     * Returns the complete list of TV inputs on the system.
1153     *
1154     * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
1155     */
1156    public List<TvInputInfo> getTvInputList() {
1157        try {
1158            return mService.getTvInputList(mUserId);
1159        } catch (RemoteException e) {
1160            throw e.rethrowFromSystemServer();
1161        }
1162    }
1163
1164    /**
1165     * Returns the {@link TvInputInfo} for a given TV input.
1166     *
1167     * @param inputId The ID of the TV input.
1168     * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
1169     */
1170    @Nullable
1171    public TvInputInfo getTvInputInfo(@NonNull String inputId) {
1172        Preconditions.checkNotNull(inputId);
1173        try {
1174            return mService.getTvInputInfo(inputId, mUserId);
1175        } catch (RemoteException e) {
1176            throw e.rethrowFromSystemServer();
1177        }
1178    }
1179
1180    /**
1181     * Updates information about an existing TV input.
1182     *
1183     * <p>This is called internally only by {@link TvInputService}.
1184     *
1185     * @param inputInfo The <code>TvInputInfo</code> object that contains new information.
1186     * @throws IllegalArgumentException if the argument is {@code null}.
1187     */
1188    void updateTvInputInfo(@NonNull TvInputInfo inputInfo) {
1189        Preconditions.checkNotNull(inputInfo);
1190        try {
1191            mService.updateTvInputInfo(inputInfo, mUserId);
1192        } catch (RemoteException e) {
1193            throw e.rethrowFromSystemServer();
1194        }
1195    }
1196
1197    /**
1198     * Returns the state of a given TV input.
1199     *
1200     * <p>The state is one of the following:
1201     * <ul>
1202     * <li>{@link #INPUT_STATE_CONNECTED}
1203     * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
1204     * <li>{@link #INPUT_STATE_DISCONNECTED}
1205     * </ul>
1206     *
1207     * @param inputId The ID of the TV input.
1208     * @throws IllegalArgumentException if the argument is {@code null}.
1209     */
1210    @InputState
1211    public int getInputState(@NonNull String inputId) {
1212        Preconditions.checkNotNull(inputId);
1213        synchronized (mLock) {
1214            Integer state = mStateMap.get(inputId);
1215            if (state == null) {
1216                Log.w(TAG, "Unrecognized input ID: " + inputId);
1217                return INPUT_STATE_DISCONNECTED;
1218            }
1219            return state;
1220        }
1221    }
1222
1223    /**
1224     * Registers a {@link TvInputCallback}.
1225     *
1226     * @param callback A callback used to monitor status of the TV inputs.
1227     * @param handler A {@link Handler} that the status change will be delivered to.
1228     */
1229    public void registerCallback(@NonNull TvInputCallback callback, @NonNull Handler handler) {
1230        Preconditions.checkNotNull(callback);
1231        Preconditions.checkNotNull(handler);
1232        synchronized (mLock) {
1233            mCallbackRecords.add(new TvInputCallbackRecord(callback, handler));
1234        }
1235    }
1236
1237    /**
1238     * Unregisters the existing {@link TvInputCallback}.
1239     *
1240     * @param callback The existing callback to remove.
1241     */
1242    public void unregisterCallback(@NonNull final TvInputCallback callback) {
1243        Preconditions.checkNotNull(callback);
1244        synchronized (mLock) {
1245            for (Iterator<TvInputCallbackRecord> it = mCallbackRecords.iterator();
1246                    it.hasNext(); ) {
1247                TvInputCallbackRecord record = it.next();
1248                if (record.getCallback() == callback) {
1249                    it.remove();
1250                    break;
1251                }
1252            }
1253        }
1254    }
1255
1256    /**
1257     * Returns the user's parental controls enabled state.
1258     *
1259     * @return {@code true} if the user enabled the parental controls, {@code false} otherwise.
1260     */
1261    public boolean isParentalControlsEnabled() {
1262        try {
1263            return mService.isParentalControlsEnabled(mUserId);
1264        } catch (RemoteException e) {
1265            throw e.rethrowFromSystemServer();
1266        }
1267    }
1268
1269    /**
1270     * Sets the user's parental controls enabled state.
1271     *
1272     * @param enabled The user's parental controls enabled state. {@code true} if the user enabled
1273     *            the parental controls, {@code false} otherwise.
1274     * @see #isParentalControlsEnabled
1275     * @hide
1276     */
1277    @SystemApi
1278    @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1279    public void setParentalControlsEnabled(boolean enabled) {
1280        try {
1281            mService.setParentalControlsEnabled(enabled, mUserId);
1282        } catch (RemoteException e) {
1283            throw e.rethrowFromSystemServer();
1284        }
1285    }
1286
1287    /**
1288     * Checks whether a given TV content rating is blocked by the user.
1289     *
1290     * @param rating The TV content rating to check. Can be {@link TvContentRating#UNRATED}.
1291     * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
1292     */
1293    public boolean isRatingBlocked(@NonNull TvContentRating rating) {
1294        Preconditions.checkNotNull(rating);
1295        try {
1296            return mService.isRatingBlocked(rating.flattenToString(), mUserId);
1297        } catch (RemoteException e) {
1298            throw e.rethrowFromSystemServer();
1299        }
1300    }
1301
1302    /**
1303     * Returns the list of blocked content ratings.
1304     *
1305     * @return the list of content ratings blocked by the user.
1306     * @hide
1307     */
1308    @SystemApi
1309    public List<TvContentRating> getBlockedRatings() {
1310        try {
1311            List<TvContentRating> ratings = new ArrayList<>();
1312            for (String rating : mService.getBlockedRatings(mUserId)) {
1313                ratings.add(TvContentRating.unflattenFromString(rating));
1314            }
1315            return ratings;
1316        } catch (RemoteException e) {
1317            throw e.rethrowFromSystemServer();
1318        }
1319    }
1320
1321    /**
1322     * Adds a user blocked content rating.
1323     *
1324     * @param rating The content rating to block.
1325     * @see #isRatingBlocked
1326     * @see #removeBlockedRating
1327     * @hide
1328     */
1329    @SystemApi
1330    @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1331    public void addBlockedRating(@NonNull TvContentRating rating) {
1332        Preconditions.checkNotNull(rating);
1333        try {
1334            mService.addBlockedRating(rating.flattenToString(), mUserId);
1335        } catch (RemoteException e) {
1336            throw e.rethrowFromSystemServer();
1337        }
1338    }
1339
1340    /**
1341     * Removes a user blocked content rating.
1342     *
1343     * @param rating The content rating to unblock.
1344     * @see #isRatingBlocked
1345     * @see #addBlockedRating
1346     * @hide
1347     */
1348    @SystemApi
1349    @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
1350    public void removeBlockedRating(@NonNull TvContentRating rating) {
1351        Preconditions.checkNotNull(rating);
1352        try {
1353            mService.removeBlockedRating(rating.flattenToString(), mUserId);
1354        } catch (RemoteException e) {
1355            throw e.rethrowFromSystemServer();
1356        }
1357    }
1358
1359    /**
1360     * Returns the list of all TV content rating systems defined.
1361     * @hide
1362     */
1363    @SystemApi
1364    public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() {
1365        try {
1366            return mService.getTvContentRatingSystemList(mUserId);
1367        } catch (RemoteException e) {
1368            throw e.rethrowFromSystemServer();
1369        }
1370    }
1371
1372    /**
1373     * Creates a {@link Session} for a given TV input.
1374     *
1375     * <p>The number of sessions that can be created at the same time is limited by the capability
1376     * of the given TV input.
1377     *
1378     * @param inputId The ID of the TV input.
1379     * @param callback A callback used to receive the created session.
1380     * @param handler A {@link Handler} that the session creation will be delivered to.
1381     * @hide
1382     */
1383    public void createSession(@NonNull String inputId, @NonNull final SessionCallback callback,
1384            @NonNull Handler handler) {
1385        createSessionInternal(inputId, false, callback, handler);
1386    }
1387
1388    /**
1389     * Creates a recording {@link Session} for a given TV input.
1390     *
1391     * <p>The number of sessions that can be created at the same time is limited by the capability
1392     * of the given TV input.
1393     *
1394     * @param inputId The ID of the TV input.
1395     * @param callback A callback used to receive the created session.
1396     * @param handler A {@link Handler} that the session creation will be delivered to.
1397     * @hide
1398     */
1399    public void createRecordingSession(@NonNull String inputId,
1400            @NonNull final SessionCallback callback, @NonNull Handler handler) {
1401        createSessionInternal(inputId, true, callback, handler);
1402    }
1403
1404    private void createSessionInternal(String inputId, boolean isRecordingSession,
1405            SessionCallback callback, Handler handler) {
1406        Preconditions.checkNotNull(inputId);
1407        Preconditions.checkNotNull(callback);
1408        Preconditions.checkNotNull(handler);
1409        SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
1410        synchronized (mSessionCallbackRecordMap) {
1411            int seq = mNextSeq++;
1412            mSessionCallbackRecordMap.put(seq, record);
1413            try {
1414                mService.createSession(mClient, inputId, isRecordingSession, seq, mUserId);
1415            } catch (RemoteException e) {
1416                throw e.rethrowFromSystemServer();
1417            }
1418        }
1419    }
1420
1421    /**
1422     * Returns the TvStreamConfig list of the given TV input.
1423     *
1424     * If you are using {@link Hardware} object from {@link
1425     * #acquireTvInputHardware}, you should get the list of available streams
1426     * from {@link HardwareCallback#onStreamConfigChanged} method, not from
1427     * here. This method is designed to be used with {@link #captureFrame} in
1428     * capture scenarios specifically and not suitable for any other use.
1429     *
1430     * @param inputId The ID of the TV input.
1431     * @return List of {@link TvStreamConfig} which is available for capturing
1432     *   of the given TV input.
1433     * @hide
1434     */
1435    @SystemApi
1436    public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId) {
1437        try {
1438            return mService.getAvailableTvStreamConfigList(inputId, mUserId);
1439        } catch (RemoteException e) {
1440            throw e.rethrowFromSystemServer();
1441        }
1442    }
1443
1444    /**
1445     * Take a snapshot of the given TV input into the provided Surface.
1446     *
1447     * @param inputId The ID of the TV input.
1448     * @param surface the {@link Surface} to which the snapshot is captured.
1449     * @param config the {@link TvStreamConfig} which is used for capturing.
1450     * @return true when the {@link Surface} is ready to be captured.
1451     * @hide
1452     */
1453    @SystemApi
1454    public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) {
1455        try {
1456            return mService.captureFrame(inputId, surface, config, mUserId);
1457        } catch (RemoteException e) {
1458            throw e.rethrowFromSystemServer();
1459        }
1460    }
1461
1462    /**
1463     * Returns true if there is only a single TV input session.
1464     *
1465     * @hide
1466     */
1467    @SystemApi
1468    public boolean isSingleSessionActive() {
1469        try {
1470            return mService.isSingleSessionActive(mUserId);
1471        } catch (RemoteException e) {
1472            throw e.rethrowFromSystemServer();
1473        }
1474    }
1475
1476    /**
1477     * Returns a list of TvInputHardwareInfo objects representing available hardware.
1478     *
1479     * @hide
1480     */
1481    @SystemApi
1482    @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1483    public List<TvInputHardwareInfo> getHardwareList() {
1484        try {
1485            return mService.getHardwareList();
1486        } catch (RemoteException e) {
1487            throw e.rethrowFromSystemServer();
1488        }
1489    }
1490
1491    /**
1492     * Returns acquired TvInputManager.Hardware object for given deviceId.
1493     *
1494     * If there are other Hardware object acquired for the same deviceId, calling this method will
1495     * preempt the previously acquired object and report {@link HardwareCallback#onReleased} to the
1496     * old object.
1497     *
1498     * @hide
1499     */
1500    @SystemApi
1501    @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1502    public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback,
1503            TvInputInfo info) {
1504        try {
1505            return new Hardware(
1506                    mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() {
1507                @Override
1508                public void onReleased() {
1509                    callback.onReleased();
1510                }
1511
1512                @Override
1513                public void onStreamConfigChanged(TvStreamConfig[] configs) {
1514                    callback.onStreamConfigChanged(configs);
1515                }
1516            }, info, mUserId));
1517        } catch (RemoteException e) {
1518            throw e.rethrowFromSystemServer();
1519        }
1520    }
1521
1522    /**
1523     * Releases previously acquired hardware object.
1524     *
1525     * @hide
1526     */
1527    @SystemApi
1528    @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
1529    public void releaseTvInputHardware(int deviceId, Hardware hardware) {
1530        try {
1531            mService.releaseTvInputHardware(deviceId, hardware.getInterface(), mUserId);
1532        } catch (RemoteException e) {
1533            throw e.rethrowFromSystemServer();
1534        }
1535    }
1536
1537    /**
1538     * Returns the list of currently available DVB devices on the system.
1539     *
1540     * @return the list of {@link DvbDeviceInfo} objects representing available DVB devices.
1541     * @hide
1542     */
1543    public List<DvbDeviceInfo> getDvbDeviceList() {
1544        try {
1545            return mService.getDvbDeviceList();
1546        } catch (RemoteException e) {
1547            throw e.rethrowFromSystemServer();
1548        }
1549    }
1550
1551    /**
1552     * Returns a {@link ParcelFileDescriptor} of a specified DVB device for a given
1553     * {@link DvbDeviceInfo}
1554     *
1555     * @param info A {@link DvbDeviceInfo} to open a DVB device.
1556     * @param device A DVB device. The DVB device can be {@link #DVB_DEVICE_DEMUX},
1557     *            {@link #DVB_DEVICE_DVR} or {@link #DVB_DEVICE_FRONTEND}.
1558     * @return a {@link ParcelFileDescriptor} of a specified DVB device for a given
1559     *         {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo} was invalid
1560     *         or the specified DVB device was busy with a previous request.
1561     * @hide
1562     */
1563    public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device) {
1564        try {
1565            if (DVB_DEVICE_START > device || DVB_DEVICE_END < device) {
1566                throw new IllegalArgumentException("Invalid DVB device: " + device);
1567            }
1568            return mService.openDvbDevice(info, device);
1569        } catch (RemoteException e) {
1570            throw e.rethrowFromSystemServer();
1571        }
1572    }
1573
1574    /**
1575     * The Session provides the per-session functionality of TV inputs.
1576     * @hide
1577     */
1578    public static final class Session {
1579        static final int DISPATCH_IN_PROGRESS = -1;
1580        static final int DISPATCH_NOT_HANDLED = 0;
1581        static final int DISPATCH_HANDLED = 1;
1582
1583        private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
1584
1585        private final ITvInputManager mService;
1586        private final int mUserId;
1587        private final int mSeq;
1588
1589        // For scheduling input event handling on the main thread. This also serves as a lock to
1590        // protect pending input events and the input channel.
1591        private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
1592
1593        private final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
1594        private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
1595        private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
1596
1597        private IBinder mToken;
1598        private TvInputEventSender mSender;
1599        private InputChannel mChannel;
1600
1601        private final Object mMetadataLock = new Object();
1602        // @GuardedBy("mMetadataLock")
1603        private final List<TvTrackInfo> mAudioTracks = new ArrayList<>();
1604        // @GuardedBy("mMetadataLock")
1605        private final List<TvTrackInfo> mVideoTracks = new ArrayList<>();
1606        // @GuardedBy("mMetadataLock")
1607        private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<>();
1608        // @GuardedBy("mMetadataLock")
1609        private String mSelectedAudioTrackId;
1610        // @GuardedBy("mMetadataLock")
1611        private String mSelectedVideoTrackId;
1612        // @GuardedBy("mMetadataLock")
1613        private String mSelectedSubtitleTrackId;
1614        // @GuardedBy("mMetadataLock")
1615        private int mVideoWidth;
1616        // @GuardedBy("mMetadataLock")
1617        private int mVideoHeight;
1618
1619        private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
1620                int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
1621            mToken = token;
1622            mChannel = channel;
1623            mService = service;
1624            mUserId = userId;
1625            mSeq = seq;
1626            mSessionCallbackRecordMap = sessionCallbackRecordMap;
1627        }
1628
1629        /**
1630         * Releases this session.
1631         */
1632        public void release() {
1633            if (mToken == null) {
1634                Log.w(TAG, "The session has been already released");
1635                return;
1636            }
1637            try {
1638                mService.releaseSession(mToken, mUserId);
1639            } catch (RemoteException e) {
1640                throw e.rethrowFromSystemServer();
1641            }
1642
1643            releaseInternal();
1644        }
1645
1646        /**
1647         * Sets this as the main session. The main session is a session whose corresponding TV
1648         * input determines the HDMI-CEC active source device.
1649         *
1650         * @see TvView#setMain
1651         */
1652        void setMain() {
1653            if (mToken == null) {
1654                Log.w(TAG, "The session has been already released");
1655                return;
1656            }
1657            try {
1658                mService.setMainSession(mToken, mUserId);
1659            } catch (RemoteException e) {
1660                throw e.rethrowFromSystemServer();
1661            }
1662        }
1663
1664        /**
1665         * Sets the {@link android.view.Surface} for this session.
1666         *
1667         * @param surface A {@link android.view.Surface} used to render video.
1668         */
1669        public void setSurface(Surface surface) {
1670            if (mToken == null) {
1671                Log.w(TAG, "The session has been already released");
1672                return;
1673            }
1674            // surface can be null.
1675            try {
1676                mService.setSurface(mToken, surface, mUserId);
1677            } catch (RemoteException e) {
1678                throw e.rethrowFromSystemServer();
1679            }
1680        }
1681
1682        /**
1683         * Notifies of any structural changes (format or size) of the surface passed in
1684         * {@link #setSurface}.
1685         *
1686         * @param format The new PixelFormat of the surface.
1687         * @param width The new width of the surface.
1688         * @param height The new height of the surface.
1689         */
1690        public void dispatchSurfaceChanged(int format, int width, int height) {
1691            if (mToken == null) {
1692                Log.w(TAG, "The session has been already released");
1693                return;
1694            }
1695            try {
1696                mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
1697            } catch (RemoteException e) {
1698                throw e.rethrowFromSystemServer();
1699            }
1700        }
1701
1702        /**
1703         * Sets the relative stream volume of this session to handle a change of audio focus.
1704         *
1705         * @param volume A volume value between 0.0f to 1.0f.
1706         * @throws IllegalArgumentException if the volume value is out of range.
1707         */
1708        public void setStreamVolume(float volume) {
1709            if (mToken == null) {
1710                Log.w(TAG, "The session has been already released");
1711                return;
1712            }
1713            try {
1714                if (volume < 0.0f || volume > 1.0f) {
1715                    throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
1716                }
1717                mService.setVolume(mToken, volume, mUserId);
1718            } catch (RemoteException e) {
1719                throw e.rethrowFromSystemServer();
1720            }
1721        }
1722
1723        /**
1724         * Tunes to a given channel.
1725         *
1726         * @param channelUri The URI of a channel.
1727         */
1728        public void tune(Uri channelUri) {
1729            tune(channelUri, null);
1730        }
1731
1732        /**
1733         * Tunes to a given channel.
1734         *
1735         * @param channelUri The URI of a channel.
1736         * @param params A set of extra parameters which might be handled with this tune event.
1737         */
1738        public void tune(@NonNull Uri channelUri, Bundle params) {
1739            Preconditions.checkNotNull(channelUri);
1740            if (mToken == null) {
1741                Log.w(TAG, "The session has been already released");
1742                return;
1743            }
1744            synchronized (mMetadataLock) {
1745                mAudioTracks.clear();
1746                mVideoTracks.clear();
1747                mSubtitleTracks.clear();
1748                mSelectedAudioTrackId = null;
1749                mSelectedVideoTrackId = null;
1750                mSelectedSubtitleTrackId = null;
1751                mVideoWidth = 0;
1752                mVideoHeight = 0;
1753            }
1754            try {
1755                mService.tune(mToken, channelUri, params, mUserId);
1756            } catch (RemoteException e) {
1757                throw e.rethrowFromSystemServer();
1758            }
1759        }
1760
1761        /**
1762         * Enables or disables the caption for this session.
1763         *
1764         * @param enabled {@code true} to enable, {@code false} to disable.
1765         */
1766        public void setCaptionEnabled(boolean enabled) {
1767            if (mToken == null) {
1768                Log.w(TAG, "The session has been already released");
1769                return;
1770            }
1771            try {
1772                mService.setCaptionEnabled(mToken, enabled, mUserId);
1773            } catch (RemoteException e) {
1774                throw e.rethrowFromSystemServer();
1775            }
1776        }
1777
1778        /**
1779         * Selects a track.
1780         *
1781         * @param type The type of the track to select. The type can be
1782         *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
1783         *            {@link TvTrackInfo#TYPE_SUBTITLE}.
1784         * @param trackId The ID of the track to select. When {@code null}, the currently selected
1785         *            track of the given type will be unselected.
1786         * @see #getTracks
1787         */
1788        public void selectTrack(int type, @Nullable String trackId) {
1789            synchronized (mMetadataLock) {
1790                if (type == TvTrackInfo.TYPE_AUDIO) {
1791                    if (trackId != null && !containsTrack(mAudioTracks, trackId)) {
1792                        Log.w(TAG, "Invalid audio trackId: " + trackId);
1793                        return;
1794                    }
1795                } else if (type == TvTrackInfo.TYPE_VIDEO) {
1796                    if (trackId != null && !containsTrack(mVideoTracks, trackId)) {
1797                        Log.w(TAG, "Invalid video trackId: " + trackId);
1798                        return;
1799                    }
1800                } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1801                    if (trackId != null && !containsTrack(mSubtitleTracks, trackId)) {
1802                        Log.w(TAG, "Invalid subtitle trackId: " + trackId);
1803                        return;
1804                    }
1805                } else {
1806                    throw new IllegalArgumentException("invalid type: " + type);
1807                }
1808            }
1809            if (mToken == null) {
1810                Log.w(TAG, "The session has been already released");
1811                return;
1812            }
1813            try {
1814                mService.selectTrack(mToken, type, trackId, mUserId);
1815            } catch (RemoteException e) {
1816                throw e.rethrowFromSystemServer();
1817            }
1818        }
1819
1820        private boolean containsTrack(List<TvTrackInfo> tracks, String trackId) {
1821            for (TvTrackInfo track : tracks) {
1822                if (track.getId().equals(trackId)) {
1823                    return true;
1824                }
1825            }
1826            return false;
1827        }
1828
1829        /**
1830         * Returns the list of tracks for a given type. Returns {@code null} if the information is
1831         * not available.
1832         *
1833         * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
1834         *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
1835         * @return the list of tracks for the given type.
1836         */
1837        @Nullable
1838        public List<TvTrackInfo> getTracks(int type) {
1839            synchronized (mMetadataLock) {
1840                if (type == TvTrackInfo.TYPE_AUDIO) {
1841                    if (mAudioTracks == null) {
1842                        return null;
1843                    }
1844                    return new ArrayList<>(mAudioTracks);
1845                } else if (type == TvTrackInfo.TYPE_VIDEO) {
1846                    if (mVideoTracks == null) {
1847                        return null;
1848                    }
1849                    return new ArrayList<>(mVideoTracks);
1850                } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1851                    if (mSubtitleTracks == null) {
1852                        return null;
1853                    }
1854                    return new ArrayList<>(mSubtitleTracks);
1855                }
1856            }
1857            throw new IllegalArgumentException("invalid type: " + type);
1858        }
1859
1860        /**
1861         * Returns the selected track for a given type. Returns {@code null} if the information is
1862         * not available or any of the tracks for the given type is not selected.
1863         *
1864         * @return The ID of the selected track.
1865         * @see #selectTrack
1866         */
1867        @Nullable
1868        public String getSelectedTrack(int type) {
1869            synchronized (mMetadataLock) {
1870                if (type == TvTrackInfo.TYPE_AUDIO) {
1871                    return mSelectedAudioTrackId;
1872                } else if (type == TvTrackInfo.TYPE_VIDEO) {
1873                    return mSelectedVideoTrackId;
1874                } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
1875                    return mSelectedSubtitleTrackId;
1876                }
1877            }
1878            throw new IllegalArgumentException("invalid type: " + type);
1879        }
1880
1881        /**
1882         * Responds to onTracksChanged() and updates the internal track information. Returns true if
1883         * there is an update.
1884         */
1885        boolean updateTracks(List<TvTrackInfo> tracks) {
1886            synchronized (mMetadataLock) {
1887                mAudioTracks.clear();
1888                mVideoTracks.clear();
1889                mSubtitleTracks.clear();
1890                for (TvTrackInfo track : tracks) {
1891                    if (track.getType() == TvTrackInfo.TYPE_AUDIO) {
1892                        mAudioTracks.add(track);
1893                    } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
1894                        mVideoTracks.add(track);
1895                    } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
1896                        mSubtitleTracks.add(track);
1897                    }
1898                }
1899                return !mAudioTracks.isEmpty() || !mVideoTracks.isEmpty()
1900                        || !mSubtitleTracks.isEmpty();
1901            }
1902        }
1903
1904        /**
1905         * Responds to onTrackSelected() and updates the internal track selection information.
1906         * Returns true if there is an update.
1907         */
1908        boolean updateTrackSelection(int type, String trackId) {
1909            synchronized (mMetadataLock) {
1910                if (type == TvTrackInfo.TYPE_AUDIO
1911                        && !TextUtils.equals(trackId, mSelectedAudioTrackId)) {
1912                    mSelectedAudioTrackId = trackId;
1913                    return true;
1914                } else if (type == TvTrackInfo.TYPE_VIDEO
1915                        && !TextUtils.equals(trackId, mSelectedVideoTrackId)) {
1916                    mSelectedVideoTrackId = trackId;
1917                    return true;
1918                } else if (type == TvTrackInfo.TYPE_SUBTITLE
1919                        && !TextUtils.equals(trackId, mSelectedSubtitleTrackId)) {
1920                    mSelectedSubtitleTrackId = trackId;
1921                    return true;
1922                }
1923            }
1924            return false;
1925        }
1926
1927        /**
1928         * Returns the new/updated video track that contains new video size information. Returns
1929         * null if there is no video track to notify. Subsequent calls of this method results in a
1930         * non-null video track returned only by the first call and null returned by following
1931         * calls. The caller should immediately notify of the video size change upon receiving the
1932         * track.
1933         */
1934        TvTrackInfo getVideoTrackToNotify() {
1935            synchronized (mMetadataLock) {
1936                if (!mVideoTracks.isEmpty() && mSelectedVideoTrackId != null) {
1937                    for (TvTrackInfo track : mVideoTracks) {
1938                        if (track.getId().equals(mSelectedVideoTrackId)) {
1939                            int videoWidth = track.getVideoWidth();
1940                            int videoHeight = track.getVideoHeight();
1941                            if (mVideoWidth != videoWidth || mVideoHeight != videoHeight) {
1942                                mVideoWidth = videoWidth;
1943                                mVideoHeight = videoHeight;
1944                                return track;
1945                            }
1946                        }
1947                    }
1948                }
1949            }
1950            return null;
1951        }
1952
1953        /**
1954         * Plays a given recorded TV program.
1955         */
1956        void timeShiftPlay(Uri recordedProgramUri) {
1957            if (mToken == null) {
1958                Log.w(TAG, "The session has been already released");
1959                return;
1960            }
1961            try {
1962                mService.timeShiftPlay(mToken, recordedProgramUri, mUserId);
1963            } catch (RemoteException e) {
1964                throw e.rethrowFromSystemServer();
1965            }
1966        }
1967
1968        /**
1969         * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback.
1970         */
1971        void timeShiftPause() {
1972            if (mToken == null) {
1973                Log.w(TAG, "The session has been already released");
1974                return;
1975            }
1976            try {
1977                mService.timeShiftPause(mToken, mUserId);
1978            } catch (RemoteException e) {
1979                throw e.rethrowFromSystemServer();
1980            }
1981        }
1982
1983        /**
1984         * Resumes the playback. No-op if it is already playing the channel.
1985         */
1986        void timeShiftResume() {
1987            if (mToken == null) {
1988                Log.w(TAG, "The session has been already released");
1989                return;
1990            }
1991            try {
1992                mService.timeShiftResume(mToken, mUserId);
1993            } catch (RemoteException e) {
1994                throw e.rethrowFromSystemServer();
1995            }
1996        }
1997
1998        /**
1999         * Seeks to a specified time position.
2000         *
2001         * <p>Normally, the position is given within range between the start and the current time,
2002         * inclusively.
2003         *
2004         * @param timeMs The time position to seek to, in milliseconds since the epoch.
2005         * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged
2006         */
2007        void timeShiftSeekTo(long timeMs) {
2008            if (mToken == null) {
2009                Log.w(TAG, "The session has been already released");
2010                return;
2011            }
2012            try {
2013                mService.timeShiftSeekTo(mToken, timeMs, mUserId);
2014            } catch (RemoteException e) {
2015                throw e.rethrowFromSystemServer();
2016            }
2017        }
2018
2019        /**
2020         * Sets playback rate using {@link android.media.PlaybackParams}.
2021         *
2022         * @param params The playback params.
2023         */
2024        void timeShiftSetPlaybackParams(PlaybackParams params) {
2025            if (mToken == null) {
2026                Log.w(TAG, "The session has been already released");
2027                return;
2028            }
2029            try {
2030                mService.timeShiftSetPlaybackParams(mToken, params, mUserId);
2031            } catch (RemoteException e) {
2032                throw e.rethrowFromSystemServer();
2033            }
2034        }
2035
2036        /**
2037         * Enable/disable position tracking.
2038         *
2039         * @param enable {@code true} to enable tracking, {@code false} otherwise.
2040         */
2041        void timeShiftEnablePositionTracking(boolean enable) {
2042            if (mToken == null) {
2043                Log.w(TAG, "The session has been already released");
2044                return;
2045            }
2046            try {
2047                mService.timeShiftEnablePositionTracking(mToken, enable, mUserId);
2048            } catch (RemoteException e) {
2049                throw e.rethrowFromSystemServer();
2050            }
2051        }
2052
2053        /**
2054         * Starts TV program recording in the current recording session.
2055         *
2056         * @param programHint The URI for the TV program to record as a hint, built by
2057         *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
2058         */
2059        void startRecording(@Nullable Uri programHint) {
2060            if (mToken == null) {
2061                Log.w(TAG, "The session has been already released");
2062                return;
2063            }
2064            try {
2065                mService.startRecording(mToken, programHint, mUserId);
2066            } catch (RemoteException e) {
2067                throw e.rethrowFromSystemServer();
2068            }
2069        }
2070
2071        /**
2072         * Stops TV program recording in the current recording session.
2073         */
2074        void stopRecording() {
2075            if (mToken == null) {
2076                Log.w(TAG, "The session has been already released");
2077                return;
2078            }
2079            try {
2080                mService.stopRecording(mToken, mUserId);
2081            } catch (RemoteException e) {
2082                throw e.rethrowFromSystemServer();
2083            }
2084        }
2085
2086        /**
2087         * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)
2088         * TvInputService.Session.appPrivateCommand()} on the current TvView.
2089         *
2090         * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
2091         *            i.e. prefixed with a package name you own, so that different developers will
2092         *            not create conflicting commands.
2093         * @param data Any data to include with the command.
2094         */
2095        public void sendAppPrivateCommand(String action, Bundle data) {
2096            if (mToken == null) {
2097                Log.w(TAG, "The session has been already released");
2098                return;
2099            }
2100            try {
2101                mService.sendAppPrivateCommand(mToken, action, data, mUserId);
2102            } catch (RemoteException e) {
2103                throw e.rethrowFromSystemServer();
2104            }
2105        }
2106
2107        /**
2108         * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
2109         * should be called whenever the layout of its containing view is changed.
2110         * {@link #removeOverlayView()} should be called to remove the overlay view.
2111         * Since a session can have only one overlay view, this method should be called only once
2112         * or it can be called again after calling {@link #removeOverlayView()}.
2113         *
2114         * @param view A view playing TV.
2115         * @param frame A position of the overlay view.
2116         * @throws IllegalStateException if {@code view} is not attached to a window.
2117         */
2118        void createOverlayView(@NonNull View view, @NonNull Rect frame) {
2119            Preconditions.checkNotNull(view);
2120            Preconditions.checkNotNull(frame);
2121            if (view.getWindowToken() == null) {
2122                throw new IllegalStateException("view must be attached to a window");
2123            }
2124            if (mToken == null) {
2125                Log.w(TAG, "The session has been already released");
2126                return;
2127            }
2128            try {
2129                mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
2130            } catch (RemoteException e) {
2131                throw e.rethrowFromSystemServer();
2132            }
2133        }
2134
2135        /**
2136         * Relayouts the current overlay view.
2137         *
2138         * @param frame A new position of the overlay view.
2139         */
2140        void relayoutOverlayView(@NonNull Rect frame) {
2141            Preconditions.checkNotNull(frame);
2142            if (mToken == null) {
2143                Log.w(TAG, "The session has been already released");
2144                return;
2145            }
2146            try {
2147                mService.relayoutOverlayView(mToken, frame, mUserId);
2148            } catch (RemoteException e) {
2149                throw e.rethrowFromSystemServer();
2150            }
2151        }
2152
2153        /**
2154         * Removes the current overlay view.
2155         */
2156        void removeOverlayView() {
2157            if (mToken == null) {
2158                Log.w(TAG, "The session has been already released");
2159                return;
2160            }
2161            try {
2162                mService.removeOverlayView(mToken, mUserId);
2163            } catch (RemoteException e) {
2164                throw e.rethrowFromSystemServer();
2165            }
2166        }
2167
2168        /**
2169         * Requests to unblock content blocked by parental controls.
2170         */
2171        void unblockContent(@NonNull TvContentRating unblockedRating) {
2172            Preconditions.checkNotNull(unblockedRating);
2173            if (mToken == null) {
2174                Log.w(TAG, "The session has been already released");
2175                return;
2176            }
2177            try {
2178                mService.unblockContent(mToken, unblockedRating.flattenToString(), mUserId);
2179            } catch (RemoteException e) {
2180                throw e.rethrowFromSystemServer();
2181            }
2182        }
2183
2184        /**
2185         * Dispatches an input event to this session.
2186         *
2187         * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
2188         * @param token A token used to identify the input event later in the callback.
2189         * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
2190         * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
2191         *            {@code null}.
2192         * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
2193         *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
2194         *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
2195         *         be invoked later.
2196         * @hide
2197         */
2198        public int dispatchInputEvent(@NonNull InputEvent event, Object token,
2199                @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
2200            Preconditions.checkNotNull(event);
2201            Preconditions.checkNotNull(callback);
2202            Preconditions.checkNotNull(handler);
2203            synchronized (mHandler) {
2204                if (mChannel == null) {
2205                    return DISPATCH_NOT_HANDLED;
2206                }
2207                PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
2208                if (Looper.myLooper() == Looper.getMainLooper()) {
2209                    // Already running on the main thread so we can send the event immediately.
2210                    return sendInputEventOnMainLooperLocked(p);
2211                }
2212
2213                // Post the event to the main thread.
2214                Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
2215                msg.setAsynchronous(true);
2216                mHandler.sendMessage(msg);
2217                return DISPATCH_IN_PROGRESS;
2218            }
2219        }
2220
2221        /**
2222         * Callback that is invoked when an input event that was dispatched to this session has been
2223         * finished.
2224         *
2225         * @hide
2226         */
2227        public interface FinishedInputEventCallback {
2228            /**
2229             * Called when the dispatched input event is finished.
2230             *
2231             * @param token A token passed to {@link #dispatchInputEvent}.
2232             * @param handled {@code true} if the dispatched input event was handled properly.
2233             *            {@code false} otherwise.
2234             */
2235            void onFinishedInputEvent(Object token, boolean handled);
2236        }
2237
2238        // Must be called on the main looper
2239        private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
2240            synchronized (mHandler) {
2241                int result = sendInputEventOnMainLooperLocked(p);
2242                if (result == DISPATCH_IN_PROGRESS) {
2243                    return;
2244                }
2245            }
2246
2247            invokeFinishedInputEventCallback(p, false);
2248        }
2249
2250        private int sendInputEventOnMainLooperLocked(PendingEvent p) {
2251            if (mChannel != null) {
2252                if (mSender == null) {
2253                    mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
2254                }
2255
2256                final InputEvent event = p.mEvent;
2257                final int seq = event.getSequenceNumber();
2258                if (mSender.sendInputEvent(seq, event)) {
2259                    mPendingEvents.put(seq, p);
2260                    Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
2261                    msg.setAsynchronous(true);
2262                    mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
2263                    return DISPATCH_IN_PROGRESS;
2264                }
2265
2266                Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
2267                        + event);
2268            }
2269            return DISPATCH_NOT_HANDLED;
2270        }
2271
2272        void finishedInputEvent(int seq, boolean handled, boolean timeout) {
2273            final PendingEvent p;
2274            synchronized (mHandler) {
2275                int index = mPendingEvents.indexOfKey(seq);
2276                if (index < 0) {
2277                    return; // spurious, event already finished or timed out
2278                }
2279
2280                p = mPendingEvents.valueAt(index);
2281                mPendingEvents.removeAt(index);
2282
2283                if (timeout) {
2284                    Log.w(TAG, "Timeout waiting for session to handle input event after "
2285                            + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
2286                } else {
2287                    mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
2288                }
2289            }
2290
2291            invokeFinishedInputEventCallback(p, handled);
2292        }
2293
2294        // Assumes the event has already been removed from the queue.
2295        void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
2296            p.mHandled = handled;
2297            if (p.mEventHandler.getLooper().isCurrentThread()) {
2298                // Already running on the callback handler thread so we can send the callback
2299                // immediately.
2300                p.run();
2301            } else {
2302                // Post the event to the callback handler thread.
2303                // In this case, the callback will be responsible for recycling the event.
2304                Message msg = Message.obtain(p.mEventHandler, p);
2305                msg.setAsynchronous(true);
2306                msg.sendToTarget();
2307            }
2308        }
2309
2310        private void flushPendingEventsLocked() {
2311            mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
2312
2313            final int count = mPendingEvents.size();
2314            for (int i = 0; i < count; i++) {
2315                int seq = mPendingEvents.keyAt(i);
2316                Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
2317                msg.setAsynchronous(true);
2318                msg.sendToTarget();
2319            }
2320        }
2321
2322        private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
2323                FinishedInputEventCallback callback, Handler handler) {
2324            PendingEvent p = mPendingEventPool.acquire();
2325            if (p == null) {
2326                p = new PendingEvent();
2327            }
2328            p.mEvent = event;
2329            p.mEventToken = token;
2330            p.mCallback = callback;
2331            p.mEventHandler = handler;
2332            return p;
2333        }
2334
2335        private void recyclePendingEventLocked(PendingEvent p) {
2336            p.recycle();
2337            mPendingEventPool.release(p);
2338        }
2339
2340        IBinder getToken() {
2341            return mToken;
2342        }
2343
2344        private void releaseInternal() {
2345            mToken = null;
2346            synchronized (mHandler) {
2347                if (mChannel != null) {
2348                    if (mSender != null) {
2349                        flushPendingEventsLocked();
2350                        mSender.dispose();
2351                        mSender = null;
2352                    }
2353                    mChannel.dispose();
2354                    mChannel = null;
2355                }
2356            }
2357            synchronized (mSessionCallbackRecordMap) {
2358                mSessionCallbackRecordMap.remove(mSeq);
2359            }
2360        }
2361
2362        private final class InputEventHandler extends Handler {
2363            public static final int MSG_SEND_INPUT_EVENT = 1;
2364            public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
2365            public static final int MSG_FLUSH_INPUT_EVENT = 3;
2366
2367            InputEventHandler(Looper looper) {
2368                super(looper, null, true);
2369            }
2370
2371            @Override
2372            public void handleMessage(Message msg) {
2373                switch (msg.what) {
2374                    case MSG_SEND_INPUT_EVENT: {
2375                        sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
2376                        return;
2377                    }
2378                    case MSG_TIMEOUT_INPUT_EVENT: {
2379                        finishedInputEvent(msg.arg1, false, true);
2380                        return;
2381                    }
2382                    case MSG_FLUSH_INPUT_EVENT: {
2383                        finishedInputEvent(msg.arg1, false, false);
2384                        return;
2385                    }
2386                }
2387            }
2388        }
2389
2390        private final class TvInputEventSender extends InputEventSender {
2391            public TvInputEventSender(InputChannel inputChannel, Looper looper) {
2392                super(inputChannel, looper);
2393            }
2394
2395            @Override
2396            public void onInputEventFinished(int seq, boolean handled) {
2397                finishedInputEvent(seq, handled, false);
2398            }
2399        }
2400
2401        private final class PendingEvent implements Runnable {
2402            public InputEvent mEvent;
2403            public Object mEventToken;
2404            public FinishedInputEventCallback mCallback;
2405            public Handler mEventHandler;
2406            public boolean mHandled;
2407
2408            public void recycle() {
2409                mEvent = null;
2410                mEventToken = null;
2411                mCallback = null;
2412                mEventHandler = null;
2413                mHandled = false;
2414            }
2415
2416            @Override
2417            public void run() {
2418                mCallback.onFinishedInputEvent(mEventToken, mHandled);
2419
2420                synchronized (mEventHandler) {
2421                    recyclePendingEventLocked(this);
2422                }
2423            }
2424        }
2425    }
2426
2427    /**
2428     * The Hardware provides the per-hardware functionality of TV hardware.
2429     *
2430     * <p>TV hardware is physical hardware attached to the Android device; for example, HDMI ports,
2431     * Component/Composite ports, etc. Specifically, logical devices such as HDMI CEC logical
2432     * devices don't fall into this category.
2433     *
2434     * @hide
2435     */
2436    @SystemApi
2437    public final static class Hardware {
2438        private final ITvInputHardware mInterface;
2439
2440        private Hardware(ITvInputHardware hardwareInterface) {
2441            mInterface = hardwareInterface;
2442        }
2443
2444        private ITvInputHardware getInterface() {
2445            return mInterface;
2446        }
2447
2448        public boolean setSurface(Surface surface, TvStreamConfig config) {
2449            try {
2450                return mInterface.setSurface(surface, config);
2451            } catch (RemoteException e) {
2452                throw new RuntimeException(e);
2453            }
2454        }
2455
2456        public void setStreamVolume(float volume) {
2457            try {
2458                mInterface.setStreamVolume(volume);
2459            } catch (RemoteException e) {
2460                throw new RuntimeException(e);
2461            }
2462        }
2463
2464        public boolean dispatchKeyEventToHdmi(KeyEvent event) {
2465            try {
2466                return mInterface.dispatchKeyEventToHdmi(event);
2467            } catch (RemoteException e) {
2468                throw new RuntimeException(e);
2469            }
2470        }
2471
2472        public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
2473                int channelMask, int format) {
2474            try {
2475                mInterface.overrideAudioSink(audioType, audioAddress, samplingRate, channelMask,
2476                        format);
2477            } catch (RemoteException e) {
2478                throw new RuntimeException(e);
2479            }
2480        }
2481    }
2482}
2483