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