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