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