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