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