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