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