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 
17 package android.media.tv;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SystemApi;
26 import android.annotation.SystemService;
27 import android.annotation.TestApi;
28 import android.content.AttributionSource;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.graphics.Rect;
32 import android.media.AudioDeviceInfo;
33 import android.media.AudioFormat.Encoding;
34 import android.media.AudioPresentation;
35 import android.media.PlaybackParams;
36 import android.media.tv.interactive.TvInteractiveAppManager;
37 import android.net.Uri;
38 import android.os.Binder;
39 import android.os.Bundle;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Looper;
43 import android.os.Message;
44 import android.os.ParcelFileDescriptor;
45 import android.os.RemoteException;
46 import android.text.TextUtils;
47 import android.util.ArrayMap;
48 import android.util.Log;
49 import android.util.Pools.Pool;
50 import android.util.Pools.SimplePool;
51 import android.util.SparseArray;
52 import android.view.InputChannel;
53 import android.view.InputEvent;
54 import android.view.InputEventSender;
55 import android.view.KeyEvent;
56 import android.view.Surface;
57 import android.view.View;
58 
59 import com.android.internal.util.Preconditions;
60 
61 import java.lang.annotation.Retention;
62 import java.lang.annotation.RetentionPolicy;
63 import java.util.ArrayList;
64 import java.util.Iterator;
65 import java.util.List;
66 import java.util.Map;
67 import java.util.Objects;
68 import java.util.concurrent.Executor;
69 
70 /**
71  * Central system API to the overall TV input framework (TIF) architecture, which arbitrates
72  * interaction between applications and the selected TV inputs.
73  *
74  * <p>There are three primary parties involved in the TV input framework (TIF) architecture:
75  *
76  * <ul>
77  * <li>The <strong>TV input manager</strong> as expressed by this class is the central point of the
78  * system that manages interaction between all other parts. It is expressed as the client-side API
79  * here which exists in each application context and communicates with a global system service that
80  * manages the interaction across all processes.
81  * <li>A <strong>TV input</strong> implemented by {@link TvInputService} represents an input source
82  * of TV, which can be a pass-through input such as HDMI, or a tuner input which provides broadcast
83  * TV programs. The system binds to the TV input per application’s request.
84  * on implementing TV inputs.
85  * <li><strong>Applications</strong> talk to the TV input manager to list TV inputs and check their
86  * status. Once an application find the input to use, it uses {@link TvView} or
87  * {@link TvRecordingClient} for further interaction such as watching and recording broadcast TV
88  * programs.
89  * </ul>
90  */
91 @SystemService(Context.TV_INPUT_SERVICE)
92 public final class TvInputManager {
93     private static final String TAG = "TvInputManager";
94 
95     static final int DVB_DEVICE_START = 0;
96     static final int DVB_DEVICE_END = 2;
97 
98     /**
99      * A demux device of DVB API for controlling the filters of DVB hardware/software.
100      * @hide
101      */
102     public static final int DVB_DEVICE_DEMUX = DVB_DEVICE_START;
103      /**
104      * A DVR device of DVB API for reading transport streams.
105      * @hide
106      */
107     public static final int DVB_DEVICE_DVR = 1;
108     /**
109      * A frontend device of DVB API for controlling the tuner and DVB demodulator hardware.
110      * @hide
111      */
112     public static final int DVB_DEVICE_FRONTEND = DVB_DEVICE_END;
113 
114     /** @hide */
115     @Retention(RetentionPolicy.SOURCE)
116     @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND})
117     public @interface DvbDeviceType {}
118 
119 
120     /** @hide */
121     @Retention(RetentionPolicy.SOURCE)
122     @IntDef({VIDEO_UNAVAILABLE_REASON_UNKNOWN, VIDEO_UNAVAILABLE_REASON_TUNING,
123             VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL, VIDEO_UNAVAILABLE_REASON_BUFFERING,
124             VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY, VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED,
125             VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE,
126             VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION,
127             VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED,
128             VIDEO_UNAVAILABLE_REASON_CAS_NO_LICENSE, VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED,
129             VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION, VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING,
130             VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD, VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE,
131             VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID, VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT,
132             VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING, VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN})
133     public @interface VideoUnavailableReason {}
134 
135     /** Indicates that this TV message contains watermarking data */
136     public static final int TV_MESSAGE_TYPE_WATERMARK = 1;
137 
138     /** Indicates that this TV message contains Closed Captioning data */
139     public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2;
140 
141     /** Indicates that this TV message contains other data */
142     public static final int TV_MESSAGE_TYPE_OTHER = 1000;
143 
144     /** @hide */
145     @Retention(RetentionPolicy.SOURCE)
146     @IntDef({TV_MESSAGE_TYPE_WATERMARK, TV_MESSAGE_TYPE_CLOSED_CAPTION, TV_MESSAGE_TYPE_OTHER})
147     public @interface TvMessageType {}
148 
149     /**
150      * This constant is used as a {@link Bundle} key for TV messages. The value of the key
151      * identifies the stream on the TV input source for which the watermark event is relevant to.
152      *
153      * <p> Type: String
154      */
155     public static final String TV_MESSAGE_KEY_STREAM_ID =
156             "android.media.tv.TvInputManager.stream_id";
157 
158     /**
159      * This value for {@link #TV_MESSAGE_KEY_GROUP_ID} denotes that the message doesn't
160      * belong to any group.
161      */
162     public static final long TV_MESSAGE_GROUP_ID_NONE = -1;
163 
164     /**
165      * This constant is used as a {@link Bundle} key for TV messages. This is used to
166      * optionally identify messages that belong together, such as headers and bodies
167      * of the same event. For messages that do not have a group, this value
168      * should be {@link #TV_MESSAGE_GROUP_ID_NONE}.
169      *
170      * <p> As -1 is a reserved value, -1 should not be used as a valid groupId.
171      *
172      * <p> Type: long
173      */
174     public static final String TV_MESSAGE_KEY_GROUP_ID =
175             "android.media.tv.TvInputManager.group_id";
176 
177     /**
178      * This is a subtype for TV messages that can be potentially found as a value
179      * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message
180      * as the watermarking format ATSC A/335.
181      */
182     public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335";
183 
184     /**
185      * This is a subtype for TV messages that can be potentially found as a value
186      * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message
187      * as the CC format CTA 608-E.
188      */
189     public static final String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E";
190 
191     /**
192      * This constant is used as a {@link Bundle} key for TV messages. The value of the key
193      * identifies the subtype of the data, such as the format of the CC data. The format
194      * found at this key can then be used to identify how to parse the data at
195      * {@link #TV_MESSAGE_KEY_RAW_DATA}.
196      *
197      * <p> To parse the raw data based on the subtype, please refer to the official
198      * documentation of the concerning subtype. For example, for the subtype
199      * {@link #TV_MESSAGE_SUBTYPE_WATERMARKING_A335}, the document for A/335 from the ATSC
200      * standard details how this data is formatted. Similarly, the subtype
201      * {@link #TV_MESSAGE_SUBTYPE_CC_608E} is documented in the ANSI/CTA standard for
202      * 608-E. These subtypes are examples of common formats for their respective uses
203      * and other subtypes may exist.
204      *
205      * <p> Type: String
206      */
207     public static final String TV_MESSAGE_KEY_SUBTYPE =
208             "android.media.tv.TvInputManager.subtype";
209 
210     /**
211      * This constant is used as a {@link Bundle} key for TV messages. The value of the key
212      * stores the raw data contained in this TV message. The format of this data is determined
213      * by the format defined by the subtype, found using the key at
214      * {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more
215      * information on how to parse this data.
216      *
217      * <p> Type: byte[]
218      */
219     public static final String TV_MESSAGE_KEY_RAW_DATA =
220             "android.media.tv.TvInputManager.raw_data";
221 
222     static final int VIDEO_UNAVAILABLE_REASON_START = 0;
223     static final int VIDEO_UNAVAILABLE_REASON_END = 18;
224 
225     /**
226      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
227      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable due to
228      * an unspecified error.
229      */
230     public static final int VIDEO_UNAVAILABLE_REASON_UNKNOWN = VIDEO_UNAVAILABLE_REASON_START;
231     /**
232      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
233      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
234      * the corresponding TV input is in the middle of tuning to a new channel.
235      */
236     public static final int VIDEO_UNAVAILABLE_REASON_TUNING = 1;
237     /**
238      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
239      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable due to
240      * weak TV signal.
241      */
242     public static final int VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL = 2;
243     /**
244      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
245      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
246      * the corresponding TV input has stopped playback temporarily to buffer more data.
247      */
248     public static final int VIDEO_UNAVAILABLE_REASON_BUFFERING = 3;
249     /**
250      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
251      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
252      * the current TV program is audio-only.
253      */
254     public static final int VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY = 4;
255     /**
256      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
257      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
258      * the source is not physically connected, for example the HDMI cable is not connected.
259      */
260     public static final int VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED = 5;
261     /**
262      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
263      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
264      * the resource is not enough to meet requirement.
265      */
266     public static final int VIDEO_UNAVAILABLE_REASON_INSUFFICIENT_RESOURCE = 6;
267     /**
268      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
269      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
270      * the output protection level enabled on the device is not sufficient to meet the requirements
271      * in the license policy.
272      */
273     public static final int VIDEO_UNAVAILABLE_REASON_CAS_INSUFFICIENT_OUTPUT_PROTECTION = 7;
274     /**
275      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
276      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
277      * the PVR record is not allowed by the license policy.
278      */
279     public static final int VIDEO_UNAVAILABLE_REASON_CAS_PVR_RECORDING_NOT_ALLOWED = 8;
280     /**
281      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
282      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
283      * no license keys have been provided.
284      * @hide
285      */
286     public static final int VIDEO_UNAVAILABLE_REASON_CAS_NO_LICENSE = 9;
287     /**
288      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
289      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
290      * Using a license in whhich the keys have expired.
291      */
292     public static final int VIDEO_UNAVAILABLE_REASON_CAS_LICENSE_EXPIRED = 10;
293     /**
294      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
295      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
296      * the device need be activated.
297      */
298     public static final int VIDEO_UNAVAILABLE_REASON_CAS_NEED_ACTIVATION = 11;
299     /**
300      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
301      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
302      * the device need be paired.
303      */
304     public static final int VIDEO_UNAVAILABLE_REASON_CAS_NEED_PAIRING = 12;
305     /**
306      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
307      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
308      * smart card is missed.
309      */
310     public static final int VIDEO_UNAVAILABLE_REASON_CAS_NO_CARD = 13;
311     /**
312      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
313      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
314      * smart card is muted.
315      */
316     public static final int VIDEO_UNAVAILABLE_REASON_CAS_CARD_MUTE = 14;
317     /**
318      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
319      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
320      * smart card is invalid.
321      */
322     public static final int VIDEO_UNAVAILABLE_REASON_CAS_CARD_INVALID = 15;
323     /**
324      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
325      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
326      * of a geographical blackout.
327      */
328     public static final int VIDEO_UNAVAILABLE_REASON_CAS_BLACKOUT = 16;
329     /**
330      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
331      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
332      * CAS system is rebooting.
333      */
334     public static final int VIDEO_UNAVAILABLE_REASON_CAS_REBOOTING = 17;
335     /**
336      * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
337      * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
338      * of unknown CAS error.
339      */
340     public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = VIDEO_UNAVAILABLE_REASON_END;
341 
342     /** @hide */
343     @Retention(RetentionPolicy.SOURCE)
344     @IntDef({TIME_SHIFT_STATUS_UNKNOWN, TIME_SHIFT_STATUS_UNSUPPORTED,
345             TIME_SHIFT_STATUS_UNAVAILABLE, TIME_SHIFT_STATUS_AVAILABLE})
346     public @interface TimeShiftStatus {}
347 
348     /**
349      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
350      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: Unknown status. Also
351      * the status prior to calling {@code notifyTimeShiftStatusChanged}.
352      */
353     public static final int TIME_SHIFT_STATUS_UNKNOWN = 0;
354 
355     /**
356      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
357      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: The current TV input
358      * does not support time shifting.
359      */
360     public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1;
361 
362     /**
363      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
364      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: Time shifting is
365      * currently unavailable but might work again later.
366      */
367     public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2;
368 
369     /**
370      * Status for {@link TvInputService.Session#notifyTimeShiftStatusChanged(int)} and
371      * {@link TvView.TvInputCallback#onTimeShiftStatusChanged(String, int)}: Time shifting is
372      * currently available. In this status, the application assumes it can pause/resume playback,
373      * seek to a specified time position and set playback rate and audio mode.
374      */
375     public static final int TIME_SHIFT_STATUS_AVAILABLE = 3;
376 
377     /**
378      * Value returned by {@link TvInputService.Session#onTimeShiftGetCurrentPosition()} and
379      * {@link TvInputService.Session#onTimeShiftGetStartPosition()} when time shifting has not
380      * yet started.
381      */
382     public static final long TIME_SHIFT_INVALID_TIME = Long.MIN_VALUE;
383 
384     /** @hide */
385     @Retention(RetentionPolicy.SOURCE)
386     @IntDef(flag = false, prefix = "TIME_SHIFT_MODE_", value = {
387             TIME_SHIFT_MODE_OFF,
388             TIME_SHIFT_MODE_LOCAL,
389             TIME_SHIFT_MODE_NETWORK,
390             TIME_SHIFT_MODE_AUTO})
391     public @interface TimeShiftMode {}
392     /**
393      * Time shift mode: off.
394      * <p>Time shift is disabled.
395      */
396     public static final int TIME_SHIFT_MODE_OFF = 1;
397     /**
398      * Time shift mode: local.
399      * <p>Time shift is handle locally, using on-device data. E.g. playing a local file.
400      */
401     public static final int TIME_SHIFT_MODE_LOCAL = 2;
402     /**
403      * Time shift mode: network.
404      * <p>Time shift is handle remotely via network. E.g. online streaming.
405      */
406     public static final int TIME_SHIFT_MODE_NETWORK = 3;
407     /**
408      * Time shift mode: auto.
409      * <p>Time shift mode is handled automatically.
410      */
411     public static final int TIME_SHIFT_MODE_AUTO = 4;
412 
413     /** @hide */
414     @Retention(RetentionPolicy.SOURCE)
415     @IntDef({RECORDING_ERROR_UNKNOWN, RECORDING_ERROR_INSUFFICIENT_SPACE,
416             RECORDING_ERROR_RESOURCE_BUSY})
417     public @interface RecordingError {}
418 
419     static final int RECORDING_ERROR_START = 0;
420     static final int RECORDING_ERROR_END = 2;
421 
422     /**
423      * Error for {@link TvInputService.RecordingSession#notifyError(int)} and
424      * {@link TvRecordingClient.RecordingCallback#onError(int)}: The requested operation cannot be
425      * completed due to a problem that does not fit under any other error codes, or the error code
426      * for the problem is defined on the higher version than application's
427      * <code>android:targetSdkVersion</code>.
428      */
429     public static final int RECORDING_ERROR_UNKNOWN = RECORDING_ERROR_START;
430 
431     /**
432      * Error for {@link TvInputService.RecordingSession#notifyError(int)} and
433      * {@link TvRecordingClient.RecordingCallback#onError(int)}: Recording cannot proceed due to
434      * insufficient storage space.
435      */
436     public static final int RECORDING_ERROR_INSUFFICIENT_SPACE = 1;
437 
438     /**
439      * Error for {@link TvInputService.RecordingSession#notifyError(int)} and
440      * {@link TvRecordingClient.RecordingCallback#onError(int)}: Recording cannot proceed because
441      * a required recording resource was not able to be allocated.
442      */
443     public static final int RECORDING_ERROR_RESOURCE_BUSY = RECORDING_ERROR_END;
444 
445     /** @hide */
446     @Retention(RetentionPolicy.SOURCE)
447     @IntDef({INPUT_STATE_CONNECTED, INPUT_STATE_CONNECTED_STANDBY, INPUT_STATE_DISCONNECTED})
448     public @interface InputState {}
449 
450     /**
451      * State for {@link #getInputState(String)} and
452      * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is connected.
453      *
454      * <p>This state indicates that a source device is connected to the input port and is in the
455      * normal operation mode. It is mostly relevant to hardware inputs such as HDMI input.
456      * Non-hardware inputs are considered connected all the time.
457      */
458     public static final int INPUT_STATE_CONNECTED = 0;
459 
460     /**
461      * State for {@link #getInputState(String)} and
462      * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is connected but
463      * in standby mode.
464      *
465      * <p>This state indicates that a source device is connected to the input port but is in standby
466      * or low power mode. It is mostly relevant to hardware inputs such as HDMI input and Component
467      * inputs.
468      */
469     public static final int INPUT_STATE_CONNECTED_STANDBY = 1;
470 
471     /**
472      * State for {@link #getInputState(String)} and
473      * {@link TvInputCallback#onInputStateChanged(String, int)}: The input source is disconnected.
474      *
475      * <p>This state indicates that a source device is disconnected from the input port. It is
476      * mostly relevant to hardware inputs such as HDMI input.
477      *
478      */
479     public static final int INPUT_STATE_DISCONNECTED = 2;
480 
481     /** @hide */
482     @Retention(RetentionPolicy.SOURCE)
483     @IntDef(prefix = "BROADCAST_INFO_TYPE_", value =
484             {BROADCAST_INFO_TYPE_TS, BROADCAST_INFO_TYPE_TABLE, BROADCAST_INFO_TYPE_SECTION,
485             BROADCAST_INFO_TYPE_PES, BROADCAST_INFO_STREAM_EVENT, BROADCAST_INFO_TYPE_DSMCC,
486             BROADCAST_INFO_TYPE_COMMAND, BROADCAST_INFO_TYPE_TIMELINE})
487     public @interface BroadcastInfoType {}
488 
489     public static final int BROADCAST_INFO_TYPE_TS = 1;
490     public static final int BROADCAST_INFO_TYPE_TABLE = 2;
491     public static final int BROADCAST_INFO_TYPE_SECTION = 3;
492     public static final int BROADCAST_INFO_TYPE_PES = 4;
493     public static final int BROADCAST_INFO_STREAM_EVENT = 5;
494     public static final int BROADCAST_INFO_TYPE_DSMCC = 6;
495     public static final int BROADCAST_INFO_TYPE_COMMAND = 7;
496     public static final int BROADCAST_INFO_TYPE_TIMELINE = 8;
497 
498     /** @hide */
499     @Retention(RetentionPolicy.SOURCE)
500     @IntDef(prefix = "SIGNAL_STRENGTH_",
501             value = {SIGNAL_STRENGTH_LOST, SIGNAL_STRENGTH_WEAK, SIGNAL_STRENGTH_STRONG})
502     public @interface SignalStrength {}
503 
504     /**
505      * Signal lost.
506      */
507     public static final int SIGNAL_STRENGTH_LOST = 1;
508     /**
509      * Weak signal.
510      */
511     public static final int SIGNAL_STRENGTH_WEAK = 2;
512     /**
513      * Strong signal.
514      */
515     public static final int SIGNAL_STRENGTH_STRONG = 3;
516 
517     /**
518      * An unknown state of the client pid gets from the TvInputManager. Client gets this value when
519      * query through {@link getClientPid(String sessionId)} fails.
520      *
521      * @hide
522      */
523     public static final int UNKNOWN_CLIENT_PID = -1;
524 
525     /**
526      * Broadcast intent action when the user blocked content ratings change. For use with the
527      * {@link #isRatingBlocked}.
528      */
529     public static final String ACTION_BLOCKED_RATINGS_CHANGED =
530             "android.media.tv.action.BLOCKED_RATINGS_CHANGED";
531 
532     /**
533      * Broadcast intent action when the parental controls enabled state changes. For use with the
534      * {@link #isParentalControlsEnabled}.
535      */
536     public static final String ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED =
537             "android.media.tv.action.PARENTAL_CONTROLS_ENABLED_CHANGED";
538 
539     /**
540      * Broadcast intent action used to query available content rating systems.
541      *
542      * <p>The TV input manager service locates available content rating systems by querying
543      * broadcast receivers that are registered for this action. An application can offer additional
544      * content rating systems to the user by declaring a suitable broadcast receiver in its
545      * manifest.
546      *
547      * <p>Here is an example broadcast receiver declaration that an application might include in its
548      * AndroidManifest.xml to advertise custom content rating systems. The meta-data specifies a
549      * resource that contains a description of each content rating system that is provided by the
550      * application.
551      *
552      * <p><pre class="prettyprint">
553      * {@literal
554      * <receiver android:name=".TvInputReceiver">
555      *     <intent-filter>
556      *         <action android:name=
557      *                 "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
558      *     </intent-filter>
559      *     <meta-data
560      *             android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
561      *             android:resource="@xml/tv_content_rating_systems" />
562      * </receiver>}</pre>
563      *
564      * <p>In the above example, the <code>@xml/tv_content_rating_systems</code> resource refers to an
565      * XML resource whose root element is <code>&lt;rating-system-definitions&gt;</code> that
566      * contains zero or more <code>&lt;rating-system-definition&gt;</code> elements. Each <code>
567      * &lt;rating-system-definition&gt;</code> element specifies the ratings, sub-ratings and rating
568      * orders of a particular content rating system.
569      *
570      * @see TvContentRating
571      */
572     public static final String ACTION_QUERY_CONTENT_RATING_SYSTEMS =
573             "android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS";
574 
575     /**
576      * Content rating systems metadata associated with {@link #ACTION_QUERY_CONTENT_RATING_SYSTEMS}.
577      *
578      * <p>Specifies the resource ID of an XML resource that describes the content rating systems
579      * that are provided by the application.
580      */
581     public static final String META_DATA_CONTENT_RATING_SYSTEMS =
582             "android.media.tv.metadata.CONTENT_RATING_SYSTEMS";
583 
584     /**
585      * Activity action to set up channel sources i.e.&nbsp;TV inputs of type
586      * {@link TvInputInfo#TYPE_TUNER}. When invoked, the system will display an appropriate UI for
587      * the user to initiate the individual setup flow provided by
588      * {@link android.R.attr#setupActivity} of each TV input service.
589      */
590     public static final String ACTION_SETUP_INPUTS = "android.media.tv.action.SETUP_INPUTS";
591 
592     /**
593      * Activity action to display the recording schedules. When invoked, the system will display an
594      * appropriate UI to browse the schedules.
595      */
596     public static final String ACTION_VIEW_RECORDING_SCHEDULES =
597             "android.media.tv.action.VIEW_RECORDING_SCHEDULES";
598 
599     private final ITvInputManager mService;
600 
601     private final Object mLock = new Object();
602 
603     // @GuardedBy("mLock")
604     private final List<TvInputCallbackRecord> mCallbackRecords = new ArrayList<>();
605 
606     // A mapping from TV input ID to the state of corresponding input.
607     // @GuardedBy("mLock")
608     private final Map<String, Integer> mStateMap = new ArrayMap<>();
609 
610     // A mapping from the sequence number of a session to its SessionCallbackRecord.
611     private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
612             new SparseArray<>();
613 
614     // A sequence number for the next session to be created. Should be protected by a lock
615     // {@code mSessionCallbackRecordMap}.
616     private int mNextSeq;
617 
618     private final ITvInputClient mClient;
619 
620     private final int mUserId;
621 
622     /**
623      * Interface used to receive the created session.
624      * @hide
625      */
626     public abstract static class SessionCallback {
627         /**
628          * This is called after {@link TvInputManager#createSession} has been processed.
629          *
630          * @param session A {@link TvInputManager.Session} instance created. This can be
631          *            {@code null} if the creation request failed.
632          */
onSessionCreated(@ullable Session session)633         public void onSessionCreated(@Nullable Session session) {
634         }
635 
636         /**
637          * This is called when {@link TvInputManager.Session} is released.
638          * This typically happens when the process hosting the session has crashed or been killed.
639          *
640          * @param session A {@link TvInputManager.Session} instance released.
641          */
onSessionReleased(Session session)642         public void onSessionReleased(Session session) {
643         }
644 
645         /**
646          * This is called when the channel of this session is changed by the underlying TV input
647          * without any {@link TvInputManager.Session#tune(Uri)} request.
648          *
649          * @param session A {@link TvInputManager.Session} associated with this callback.
650          * @param channelUri The URI of a channel.
651          */
onChannelRetuned(Session session, Uri channelUri)652         public void onChannelRetuned(Session session, Uri channelUri) {
653         }
654 
655         /**
656          * This is called when the audio presentation information of the session has been changed.
657          *
658          * @param session A {@link TvInputManager.Session} associated with this callback.
659          * @param audioPresentations An updated list of selectable audio presentations.
660          */
onAudioPresentationsChanged(Session session, List<AudioPresentation> audioPresentations)661         public void onAudioPresentationsChanged(Session session,
662                 List<AudioPresentation> audioPresentations) {
663         }
664 
665         /**
666          * This is called when an audio presentation is selected.
667          *
668          * @param session A {@link TvInputManager.Session} associated with this callback.
669          * @param presentationId The ID of the selected audio presentation.
670          * @param programId The ID of the program providing the selected audio presentation.
671          */
onAudioPresentationSelected(Session session, int presentationId, int programId)672         public void onAudioPresentationSelected(Session session, int presentationId,
673                 int programId) {
674         }
675 
676         /**
677          * This is called when the track information of the session has been changed.
678          *
679          * @param session A {@link TvInputManager.Session} associated with this callback.
680          * @param tracks A list which includes track information.
681          */
onTracksChanged(Session session, List<TvTrackInfo> tracks)682         public void onTracksChanged(Session session, List<TvTrackInfo> tracks) {
683         }
684 
685         /**
686          * This is called when a track for a given type is selected.
687          *
688          * @param session A {@link TvInputManager.Session} associated with this callback.
689          * @param type The type of the selected track. The type can be
690          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
691          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
692          * @param trackId The ID of the selected track. When {@code null} the currently selected
693          *            track for a given type should be unselected.
694          */
onTrackSelected(Session session, int type, @Nullable String trackId)695         public void onTrackSelected(Session session, int type, @Nullable String trackId) {
696         }
697 
698         /**
699          * This is invoked when the video size has been changed. It is also called when the first
700          * time video size information becomes available after the session is tuned to a specific
701          * channel.
702          *
703          * @param session A {@link TvInputManager.Session} associated with this callback.
704          * @param width The width of the video.
705          * @param height The height of the video.
706          */
onVideoSizeChanged(Session session, int width, int height)707         public void onVideoSizeChanged(Session session, int width, int height) {
708         }
709 
710         /**
711          * This is called when the video is available, so the TV input starts the playback.
712          *
713          * @param session A {@link TvInputManager.Session} associated with this callback.
714          */
onVideoAvailable(Session session)715         public void onVideoAvailable(Session session) {
716         }
717 
718         /**
719          * This is called when the video is not available, so the TV input stops the playback.
720          *
721          * @param session A {@link TvInputManager.Session} associated with this callback.
722          * @param reason The reason why the TV input stopped the playback:
723          * <ul>
724          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
725          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
726          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
727          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
728          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
729          * </ul>
730          */
onVideoUnavailable(Session session, int reason)731         public void onVideoUnavailable(Session session, int reason) {
732         }
733 
734         /**
735          * This is called when the current program content turns out to be allowed to watch since
736          * its content rating is not blocked by parental controls.
737          *
738          * @param session A {@link TvInputManager.Session} associated with this callback.
739          */
onContentAllowed(Session session)740         public void onContentAllowed(Session session) {
741         }
742 
743         /**
744          * This is called when the current program content turns out to be not allowed to watch
745          * since its content rating is blocked by parental controls.
746          *
747          * @param session A {@link TvInputManager.Session} associated with this callback.
748          * @param rating The content ration of the blocked program.
749          */
onContentBlocked(Session session, TvContentRating rating)750         public void onContentBlocked(Session session, TvContentRating rating) {
751         }
752 
753         /**
754          * This is called when {@link TvInputService.Session#layoutSurface} is called to change the
755          * layout of surface.
756          *
757          * @param session A {@link TvInputManager.Session} associated with this callback.
758          * @param left Left position.
759          * @param top Top position.
760          * @param right Right position.
761          * @param bottom Bottom position.
762          */
onLayoutSurface(Session session, int left, int top, int right, int bottom)763         public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
764         }
765 
766         /**
767          * This is called when a custom event has been sent from this session.
768          *
769          * @param session A {@link TvInputManager.Session} associated with this callback
770          * @param eventType The type of the event.
771          * @param eventArgs Optional arguments of the event.
772          */
onSessionEvent(Session session, String eventType, Bundle eventArgs)773         public void onSessionEvent(Session session, String eventType, Bundle eventArgs) {
774         }
775 
776         /**
777          * This is called when the time shift status is changed.
778          *
779          * @param session A {@link TvInputManager.Session} associated with this callback.
780          * @param status The current time shift status. Should be one of the followings.
781          * <ul>
782          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
783          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
784          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
785          * </ul>
786          */
onTimeShiftStatusChanged(Session session, int status)787         public void onTimeShiftStatusChanged(Session session, int status) {
788         }
789 
790         /**
791          * This is called when the start position for time shifting has changed.
792          *
793          * @param session A {@link TvInputManager.Session} associated with this callback.
794          * @param timeMs The start position for time shifting, in milliseconds since the epoch.
795          */
onTimeShiftStartPositionChanged(Session session, long timeMs)796         public void onTimeShiftStartPositionChanged(Session session, long timeMs) {
797         }
798 
799         /**
800          * This is called when the current position for time shifting is changed.
801          *
802          * @param session A {@link TvInputManager.Session} associated with this callback.
803          * @param timeMs The current position for time shifting, in milliseconds since the epoch.
804          */
onTimeShiftCurrentPositionChanged(Session session, long timeMs)805         public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) {
806         }
807 
808         /**
809          * This is called when AIT info is updated.
810          * @param session A {@link TvInputManager.Session} associated with this callback.
811          * @param aitInfo The current AIT info.
812          */
onAitInfoUpdated(Session session, AitInfo aitInfo)813         public void onAitInfoUpdated(Session session, AitInfo aitInfo) {
814         }
815 
816         /**
817          * This is called when signal strength is updated.
818          * @param session A {@link TvInputManager.Session} associated with this callback.
819          * @param strength The current signal strength.
820          */
onSignalStrengthUpdated(Session session, @SignalStrength int strength)821         public void onSignalStrengthUpdated(Session session, @SignalStrength int strength) {
822         }
823 
824         /**
825          * This is called when cueing message becomes available or unavailable.
826          * @param session A {@link TvInputManager.Session} associated with this callback.
827          * @param available The current availability of cueing message. {@code true} if cueing
828          *                  message is available; {@code false} if it becomes unavailable.
829          */
onCueingMessageAvailability(Session session, boolean available)830         public void onCueingMessageAvailability(Session session, boolean available) {
831         }
832 
833         /**
834          * This is called when time shift mode is set or updated.
835          * @param session A {@link TvInputManager.Session} associated with this callback.
836          * @param mode The current time shift mode. The value is one of the following:
837          * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
838          * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
839          * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
840          */
onTimeShiftMode(Session session, @TimeShiftMode int mode)841         public void onTimeShiftMode(Session session, @TimeShiftMode int mode) {
842         }
843 
844         /**
845          * Informs the app available speeds for time-shifting.
846          * @param session A {@link TvInputManager.Session} associated with this callback.
847          * @param speeds An ordered array of playback speeds, expressed as values relative to the
848          *               normal playback speed (1.0), at which the current content can be played as
849          *               a time-shifted broadcast. This is an empty array if the supported playback
850          *               speeds are unknown or the video/broadcast is not in time shift mode. If
851          *               currently in time shift mode, this array will normally include at least
852          *               the values 1.0 (normal speed) and 0.0 (paused).
853          * @see PlaybackParams#getSpeed()
854          */
onAvailableSpeeds(Session session, float[] speeds)855         public void onAvailableSpeeds(Session session, float[] speeds) {
856         }
857 
858         /**
859          * This is called when the session has been tuned to the given channel.
860          *
861          * @param channelUri The URI of a channel.
862          */
onTuned(Session session, Uri channelUri)863         public void onTuned(Session session, Uri channelUri) {
864         }
865 
866         /**
867          * This is called when the session receives a new TV Message
868          *
869          * @param session A {@link TvInputManager.Session} associated with this callback.
870          * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK}
871          * @param data The raw data of the message. The bundle keys are:
872          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
873          *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
874          *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
875          *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
876          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
877          *             how to parse this data.
878          *
879          */
onTvMessage(Session session, @TvInputManager.TvMessageType int type, Bundle data)880         public void onTvMessage(Session session, @TvInputManager.TvMessageType int type,
881                 Bundle data) {
882         }
883 
884         // For the recording session only
885         /**
886          * This is called when the current recording session has stopped recording and created a
887          * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly
888          * recorded program.
889          *
890          * @param recordedProgramUri The URI for the newly recorded program.
891          **/
onRecordingStopped(Session session, Uri recordedProgramUri)892         void onRecordingStopped(Session session, Uri recordedProgramUri) {
893         }
894 
895         // For the recording session only
896         /**
897          * This is called when an issue has occurred. It may be called at any time after the current
898          * recording session is created until it is released.
899          *
900          * @param error The error code.
901          */
onError(Session session, @TvInputManager.RecordingError int error)902         void onError(Session session, @TvInputManager.RecordingError int error) {
903         }
904     }
905 
906     private static final class SessionCallbackRecord {
907         private final SessionCallback mSessionCallback;
908         private final Handler mHandler;
909         private Session mSession;
910 
SessionCallbackRecord(SessionCallback sessionCallback, Handler handler)911         SessionCallbackRecord(SessionCallback sessionCallback,
912                 Handler handler) {
913             mSessionCallback = sessionCallback;
914             mHandler = handler;
915         }
916 
postSessionCreated(final Session session)917         void postSessionCreated(final Session session) {
918             mSession = session;
919             mHandler.post(new Runnable() {
920                 @Override
921                 public void run() {
922                     mSessionCallback.onSessionCreated(session);
923                 }
924             });
925         }
926 
postSessionReleased()927         void postSessionReleased() {
928             mHandler.post(new Runnable() {
929                 @Override
930                 public void run() {
931                     mSessionCallback.onSessionReleased(mSession);
932                 }
933             });
934         }
935 
postChannelRetuned(final Uri channelUri)936         void postChannelRetuned(final Uri channelUri) {
937             mHandler.post(new Runnable() {
938                 @Override
939                 public void run() {
940                     mSessionCallback.onChannelRetuned(mSession, channelUri);
941                 }
942             });
943         }
944 
postAudioPresentationsChanged(final List<AudioPresentation> audioPresentations)945         void postAudioPresentationsChanged(final List<AudioPresentation> audioPresentations) {
946             mHandler.post(new Runnable() {
947                 @Override
948                 public void run() {
949                     mSessionCallback.onAudioPresentationsChanged(mSession, audioPresentations);
950                 }
951             });
952         }
953 
postAudioPresentationSelected(final int presentationId, final int programId)954         void postAudioPresentationSelected(final int presentationId, final int programId) {
955             mHandler.post(new Runnable() {
956                 @Override
957                 public void run() {
958                     mSessionCallback.onAudioPresentationSelected(mSession, presentationId,
959                             programId);
960                 }
961             });
962         }
963 
postTracksChanged(final List<TvTrackInfo> tracks)964         void postTracksChanged(final List<TvTrackInfo> tracks) {
965             mHandler.post(new Runnable() {
966                 @Override
967                 public void run() {
968                     mSessionCallback.onTracksChanged(mSession, tracks);
969                     if (mSession.mIAppNotificationEnabled
970                             && mSession.getInteractiveAppSession() != null) {
971                         mSession.getInteractiveAppSession().notifyTracksChanged(tracks);
972                     }
973                 }
974             });
975         }
976 
postTrackSelected(final int type, final String trackId)977         void postTrackSelected(final int type, final String trackId) {
978             mHandler.post(new Runnable() {
979                 @Override
980                 public void run() {
981                     mSessionCallback.onTrackSelected(mSession, type, trackId);
982                     if (mSession.mIAppNotificationEnabled
983                             && mSession.getInteractiveAppSession() != null) {
984                         mSession.getInteractiveAppSession().notifyTrackSelected(type, trackId);
985                     }
986                 }
987             });
988         }
989 
postVideoSizeChanged(final int width, final int height)990         void postVideoSizeChanged(final int width, final int height) {
991             mHandler.post(new Runnable() {
992                 @Override
993                 public void run() {
994                     mSessionCallback.onVideoSizeChanged(mSession, width, height);
995                 }
996             });
997         }
998 
postVideoAvailable()999         void postVideoAvailable() {
1000             mHandler.post(new Runnable() {
1001                 @Override
1002                 public void run() {
1003                     mSessionCallback.onVideoAvailable(mSession);
1004                     if (mSession.mIAppNotificationEnabled
1005                             && mSession.getInteractiveAppSession() != null) {
1006                         mSession.getInteractiveAppSession().notifyVideoAvailable();
1007                     }
1008                 }
1009             });
1010         }
1011 
postVideoUnavailable(final int reason)1012         void postVideoUnavailable(final int reason) {
1013             mHandler.post(new Runnable() {
1014                 @Override
1015                 public void run() {
1016                     mSessionCallback.onVideoUnavailable(mSession, reason);
1017                     if (mSession.mIAppNotificationEnabled
1018                             && mSession.getInteractiveAppSession() != null) {
1019                         mSession.getInteractiveAppSession().notifyVideoUnavailable(reason);
1020                     }
1021                 }
1022             });
1023         }
1024 
postContentAllowed()1025         void postContentAllowed() {
1026             mHandler.post(new Runnable() {
1027                 @Override
1028                 public void run() {
1029                     mSessionCallback.onContentAllowed(mSession);
1030                     if (mSession.mIAppNotificationEnabled
1031                             && mSession.getInteractiveAppSession() != null) {
1032                         mSession.getInteractiveAppSession().notifyContentAllowed();
1033                     }
1034                 }
1035             });
1036         }
1037 
postContentBlocked(final TvContentRating rating)1038         void postContentBlocked(final TvContentRating rating) {
1039             mHandler.post(new Runnable() {
1040                 @Override
1041                 public void run() {
1042                     mSessionCallback.onContentBlocked(mSession, rating);
1043                     if (mSession.mIAppNotificationEnabled
1044                             && mSession.getInteractiveAppSession() != null) {
1045                         mSession.getInteractiveAppSession().notifyContentBlocked(rating);
1046                     }
1047                 }
1048             });
1049         }
1050 
postLayoutSurface(final int left, final int top, final int right, final int bottom)1051         void postLayoutSurface(final int left, final int top, final int right,
1052                 final int bottom) {
1053             mHandler.post(new Runnable() {
1054                 @Override
1055                 public void run() {
1056                     mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
1057                 }
1058             });
1059         }
1060 
postSessionEvent(final String eventType, final Bundle eventArgs)1061         void postSessionEvent(final String eventType, final Bundle eventArgs) {
1062             mHandler.post(new Runnable() {
1063                 @Override
1064                 public void run() {
1065                     mSessionCallback.onSessionEvent(mSession, eventType, eventArgs);
1066                 }
1067             });
1068         }
1069 
postTimeShiftStatusChanged(final int status)1070         void postTimeShiftStatusChanged(final int status) {
1071             mHandler.post(new Runnable() {
1072                 @Override
1073                 public void run() {
1074                     mSessionCallback.onTimeShiftStatusChanged(mSession, status);
1075                 }
1076             });
1077         }
1078 
postTimeShiftStartPositionChanged(final long timeMs)1079         void postTimeShiftStartPositionChanged(final long timeMs) {
1080             mHandler.post(new Runnable() {
1081                 @Override
1082                 public void run() {
1083                     mSessionCallback.onTimeShiftStartPositionChanged(mSession, timeMs);
1084                 }
1085             });
1086         }
1087 
postTimeShiftCurrentPositionChanged(final long timeMs)1088         void postTimeShiftCurrentPositionChanged(final long timeMs) {
1089             mHandler.post(new Runnable() {
1090                 @Override
1091                 public void run() {
1092                     mSessionCallback.onTimeShiftCurrentPositionChanged(mSession, timeMs);
1093                 }
1094             });
1095         }
1096 
postAitInfoUpdated(final AitInfo aitInfo)1097         void postAitInfoUpdated(final AitInfo aitInfo) {
1098             mHandler.post(new Runnable() {
1099                 @Override
1100                 public void run() {
1101                     mSessionCallback.onAitInfoUpdated(mSession, aitInfo);
1102                 }
1103             });
1104         }
1105 
postSignalStrength(final int strength)1106         void postSignalStrength(final int strength) {
1107             mHandler.post(new Runnable() {
1108                 @Override
1109                 public void run() {
1110                     mSessionCallback.onSignalStrengthUpdated(mSession, strength);
1111                     if (mSession.mIAppNotificationEnabled
1112                             && mSession.getInteractiveAppSession() != null) {
1113                         mSession.getInteractiveAppSession().notifySignalStrength(strength);
1114                     }
1115                 }
1116             });
1117         }
1118 
postCueingMessageAvailability(final boolean available)1119         void postCueingMessageAvailability(final boolean available) {
1120             mHandler.post(new Runnable() {
1121                 @Override
1122                 public void run() {
1123                     mSessionCallback.onCueingMessageAvailability(mSession, available);
1124                 }
1125             });
1126         }
1127 
postTimeShiftMode(final int mode)1128         void postTimeShiftMode(final int mode) {
1129             mHandler.post(new Runnable() {
1130                 @Override
1131                 public void run() {
1132                     mSessionCallback.onTimeShiftMode(mSession, mode);
1133                 }
1134             });
1135         }
1136 
postAvailableSpeeds(float[] speeds)1137         void postAvailableSpeeds(float[] speeds) {
1138             mHandler.post(new Runnable() {
1139                 @Override
1140                 public void run() {
1141                     mSessionCallback.onAvailableSpeeds(mSession, speeds);
1142                 }
1143             });
1144         }
1145 
postTuned(final Uri channelUri)1146         void postTuned(final Uri channelUri) {
1147             mHandler.post(new Runnable() {
1148                 @Override
1149                 public void run() {
1150                     mSessionCallback.onTuned(mSession, channelUri);
1151                     if (mSession.mIAppNotificationEnabled
1152                             && mSession.getInteractiveAppSession() != null) {
1153                         mSession.getInteractiveAppSession().notifyTuned(channelUri);
1154                     }
1155                 }
1156             });
1157         }
1158 
postTvMessage(int type, Bundle data)1159         void postTvMessage(int type, Bundle data) {
1160             mHandler.post(new Runnable() {
1161                 @Override
1162                 public void run() {
1163                     mSessionCallback.onTvMessage(mSession, type, data);
1164                     if (mSession.mIAppNotificationEnabled
1165                             && mSession.getInteractiveAppSession() != null) {
1166                         mSession.getInteractiveAppSession().notifyTvMessage(type, data);
1167                     }
1168                 }
1169             });
1170         }
1171 
1172         // For the recording session only
postRecordingStopped(final Uri recordedProgramUri)1173         void postRecordingStopped(final Uri recordedProgramUri) {
1174             mHandler.post(new Runnable() {
1175                 @Override
1176                 public void run() {
1177                     mSessionCallback.onRecordingStopped(mSession, recordedProgramUri);
1178                 }
1179             });
1180         }
1181 
1182         // For the recording session only
postError(final int error)1183         void postError(final int error) {
1184             mHandler.post(new Runnable() {
1185                 @Override
1186                 public void run() {
1187                     mSessionCallback.onError(mSession, error);
1188                 }
1189             });
1190         }
1191 
postBroadcastInfoResponse(final BroadcastInfoResponse response)1192         void postBroadcastInfoResponse(final BroadcastInfoResponse response) {
1193             if (mSession.mIAppNotificationEnabled) {
1194                 mHandler.post(new Runnable() {
1195                     @Override
1196                     public void run() {
1197                         if (mSession.getInteractiveAppSession() != null) {
1198                             mSession.getInteractiveAppSession()
1199                                     .notifyBroadcastInfoResponse(response);
1200                         }
1201                     }
1202                 });
1203             }
1204         }
1205 
postAdResponse(final AdResponse response)1206         void postAdResponse(final AdResponse response) {
1207             if (mSession.mIAppNotificationEnabled) {
1208                 mHandler.post(new Runnable() {
1209                     @Override
1210                     public void run() {
1211                         if (mSession.getInteractiveAppSession() != null) {
1212                             mSession.getInteractiveAppSession().notifyAdResponse(response);
1213                         }
1214                     }
1215                 });
1216             }
1217         }
1218 
postAdBufferConsumed(AdBuffer buffer)1219         void postAdBufferConsumed(AdBuffer buffer) {
1220             if (mSession.mIAppNotificationEnabled) {
1221                 mHandler.post(new Runnable() {
1222                     @Override
1223                     public void run() {
1224                         if (mSession.getInteractiveAppSession() != null) {
1225                             mSession.getInteractiveAppSession().notifyAdBufferConsumed(buffer);
1226                         }
1227                     }
1228                 });
1229             }
1230         }
1231     }
1232 
1233     /**
1234      * Callback used to monitor status of the TV inputs.
1235      */
1236     public abstract static class TvInputCallback {
1237         /**
1238          * This is called when the state of a given TV input is changed.
1239          *
1240          * @param inputId The ID of the TV input.
1241          * @param state State of the TV input. The value is one of the following:
1242          * <ul>
1243          * <li>{@link TvInputManager#INPUT_STATE_CONNECTED}
1244          * <li>{@link TvInputManager#INPUT_STATE_CONNECTED_STANDBY}
1245          * <li>{@link TvInputManager#INPUT_STATE_DISCONNECTED}
1246          * </ul>
1247          */
onInputStateChanged(String inputId, @InputState int state)1248         public void onInputStateChanged(String inputId, @InputState int state) {
1249         }
1250 
1251         /**
1252          * This is called when a TV input is added to the system.
1253          *
1254          * <p>Normally it happens when the user installs a new TV input package that implements
1255          * {@link TvInputService} interface.
1256          *
1257          * @param inputId The ID of the TV input.
1258          */
onInputAdded(String inputId)1259         public void onInputAdded(String inputId) {
1260         }
1261 
1262         /**
1263          * This is called when a TV input is removed from the system.
1264          *
1265          * <p>Normally it happens when the user uninstalls the previously installed TV input
1266          * package.
1267          *
1268          * @param inputId The ID of the TV input.
1269          */
onInputRemoved(String inputId)1270         public void onInputRemoved(String inputId) {
1271         }
1272 
1273         /**
1274          * This is called when a TV input is updated on the system.
1275          *
1276          * <p>Normally it happens when a previously installed TV input package is re-installed or
1277          * the media on which a newer version of the package exists becomes available/unavailable.
1278          *
1279          * @param inputId The ID of the TV input.
1280          */
onInputUpdated(String inputId)1281         public void onInputUpdated(String inputId) {
1282         }
1283 
1284         /**
1285          * This is called when the information about an existing TV input has been updated.
1286          *
1287          * <p>Because the system automatically creates a <code>TvInputInfo</code> object for each TV
1288          * input based on the information collected from the <code>AndroidManifest.xml</code>, this
1289          * method is only called back when such information has changed dynamically.
1290          *
1291          * @param inputInfo The <code>TvInputInfo</code> object that contains new information.
1292          */
onTvInputInfoUpdated(TvInputInfo inputInfo)1293         public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
1294         }
1295 
1296         /**
1297          * This is called when the information about current tuned information has been updated.
1298          *
1299          * @param tunedInfos a list of {@link TunedInfo} objects of new tuned information.
1300          * @hide
1301          */
1302         @SystemApi
1303         @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO)
onCurrentTunedInfosUpdated(@onNull List<TunedInfo> tunedInfos)1304         public void onCurrentTunedInfosUpdated(@NonNull List<TunedInfo> tunedInfos) {
1305         }
1306     }
1307 
1308     private static final class TvInputCallbackRecord {
1309         private final TvInputCallback mCallback;
1310         private final Handler mHandler;
1311 
TvInputCallbackRecord(TvInputCallback callback, Handler handler)1312         public TvInputCallbackRecord(TvInputCallback callback, Handler handler) {
1313             mCallback = callback;
1314             mHandler = handler;
1315         }
1316 
getCallback()1317         public TvInputCallback getCallback() {
1318             return mCallback;
1319         }
1320 
postInputAdded(final String inputId)1321         public void postInputAdded(final String inputId) {
1322             mHandler.post(new Runnable() {
1323                 @Override
1324                 public void run() {
1325                     mCallback.onInputAdded(inputId);
1326                 }
1327             });
1328         }
1329 
postInputRemoved(final String inputId)1330         public void postInputRemoved(final String inputId) {
1331             mHandler.post(new Runnable() {
1332                 @Override
1333                 public void run() {
1334                     mCallback.onInputRemoved(inputId);
1335                 }
1336             });
1337         }
1338 
postInputUpdated(final String inputId)1339         public void postInputUpdated(final String inputId) {
1340             mHandler.post(new Runnable() {
1341                 @Override
1342                 public void run() {
1343                     mCallback.onInputUpdated(inputId);
1344                 }
1345             });
1346         }
1347 
postInputStateChanged(final String inputId, final int state)1348         public void postInputStateChanged(final String inputId, final int state) {
1349             mHandler.post(new Runnable() {
1350                 @Override
1351                 public void run() {
1352                     mCallback.onInputStateChanged(inputId, state);
1353                 }
1354             });
1355         }
1356 
postTvInputInfoUpdated(final TvInputInfo inputInfo)1357         public void postTvInputInfoUpdated(final TvInputInfo inputInfo) {
1358             mHandler.post(new Runnable() {
1359                 @Override
1360                 public void run() {
1361                     mCallback.onTvInputInfoUpdated(inputInfo);
1362                 }
1363             });
1364         }
1365 
postCurrentTunedInfosUpdated(final List<TunedInfo> currentTunedInfos)1366         public void postCurrentTunedInfosUpdated(final List<TunedInfo> currentTunedInfos) {
1367             mHandler.post(new Runnable() {
1368                 @Override
1369                 public void run() {
1370                     mCallback.onCurrentTunedInfosUpdated(currentTunedInfos);
1371                 }
1372             });
1373         }
1374     }
1375 
1376     /**
1377      * Interface used to receive events from Hardware objects.
1378      *
1379      * @hide
1380      */
1381     @SystemApi
1382     public abstract static class HardwareCallback {
1383         /**
1384          * This is called when {@link Hardware} is no longer available for the client.
1385          */
onReleased()1386         public abstract void onReleased();
1387 
1388         /**
1389          * This is called when the underlying {@link TvStreamConfig} has been changed.
1390          *
1391          * @param configs The new {@link TvStreamConfig}s.
1392          */
onStreamConfigChanged(TvStreamConfig[] configs)1393         public abstract void onStreamConfigChanged(TvStreamConfig[] configs);
1394     }
1395 
1396     /**
1397      * @hide
1398      */
TvInputManager(ITvInputManager service, int userId)1399     public TvInputManager(ITvInputManager service, int userId) {
1400         mService = service;
1401         mUserId = userId;
1402         mClient = new ITvInputClient.Stub() {
1403             @Override
1404             public void onSessionCreated(String inputId, IBinder token, InputChannel channel,
1405                     int seq) {
1406                 synchronized (mSessionCallbackRecordMap) {
1407                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1408                     if (record == null) {
1409                         Log.e(TAG, "Callback not found for " + token);
1410                         return;
1411                     }
1412                     Session session = null;
1413                     if (token != null) {
1414                         session = new Session(token, channel, mService, mUserId, seq,
1415                                 mSessionCallbackRecordMap);
1416                     } else {
1417                         mSessionCallbackRecordMap.delete(seq);
1418                     }
1419                     record.postSessionCreated(session);
1420                 }
1421             }
1422 
1423             @Override
1424             public void onSessionReleased(int seq) {
1425                 synchronized (mSessionCallbackRecordMap) {
1426                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1427                     mSessionCallbackRecordMap.delete(seq);
1428                     if (record == null) {
1429                         Log.e(TAG, "Callback not found for seq:" + seq);
1430                         return;
1431                     }
1432                     record.mSession.releaseInternal();
1433                     record.postSessionReleased();
1434                 }
1435             }
1436 
1437             @Override
1438             public void onChannelRetuned(Uri channelUri, int seq) {
1439                 synchronized (mSessionCallbackRecordMap) {
1440                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1441                     if (record == null) {
1442                         Log.e(TAG, "Callback not found for seq " + seq);
1443                         return;
1444                     }
1445                     record.postChannelRetuned(channelUri);
1446                 }
1447             }
1448             @Override
1449             public void onAudioPresentationsChanged(List<AudioPresentation> audioPresentations,
1450                     int seq) {
1451                 synchronized (mSessionCallbackRecordMap) {
1452                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1453                     if (record == null) {
1454                         Log.e(TAG, "Callback not found for seq " + seq);
1455                         return;
1456                     }
1457                     if (record.mSession.updateAudioPresentations(audioPresentations)) {
1458                         record.postAudioPresentationsChanged(audioPresentations);
1459                     }
1460                 }
1461             }
1462 
1463             @Override
1464             public void onAudioPresentationSelected(int presentationId, int programId, int seq) {
1465                 synchronized (mSessionCallbackRecordMap) {
1466                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1467                     if (record == null) {
1468                         Log.e(TAG, "Callback not found for seq " + seq);
1469                         return;
1470                     }
1471                     if (record.mSession.updateAudioPresentationSelection(presentationId,
1472                             programId)) {
1473                         record.postAudioPresentationSelected(presentationId, programId);
1474                     }
1475                 }
1476             }
1477 
1478 
1479             @Override
1480             public void onTracksChanged(List<TvTrackInfo> tracks, int seq) {
1481                 synchronized (mSessionCallbackRecordMap) {
1482                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1483                     if (record == null) {
1484                         Log.e(TAG, "Callback not found for seq " + seq);
1485                         return;
1486                     }
1487                     if (record.mSession.updateTracks(tracks)) {
1488                         record.postTracksChanged(tracks);
1489                         postVideoSizeChangedIfNeededLocked(record);
1490                     }
1491                 }
1492             }
1493 
1494             @Override
1495             public void onTrackSelected(int type, String trackId, int seq) {
1496                 synchronized (mSessionCallbackRecordMap) {
1497                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1498                     if (record == null) {
1499                         Log.e(TAG, "Callback not found for seq " + seq);
1500                         return;
1501                     }
1502                     if (record.mSession.updateTrackSelection(type, trackId)) {
1503                         record.postTrackSelected(type, trackId);
1504                         postVideoSizeChangedIfNeededLocked(record);
1505                     }
1506                 }
1507             }
1508 
1509             private void postVideoSizeChangedIfNeededLocked(SessionCallbackRecord record) {
1510                 TvTrackInfo track = record.mSession.getVideoTrackToNotify();
1511                 if (track != null) {
1512                     record.postVideoSizeChanged(track.getVideoWidth(), track.getVideoHeight());
1513                 }
1514             }
1515 
1516             @Override
1517             public void onVideoAvailable(int seq) {
1518                 synchronized (mSessionCallbackRecordMap) {
1519                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1520                     if (record == null) {
1521                         Log.e(TAG, "Callback not found for seq " + seq);
1522                         return;
1523                     }
1524                     record.postVideoAvailable();
1525                 }
1526             }
1527 
1528             @Override
1529             public void onVideoUnavailable(int reason, int seq) {
1530                 synchronized (mSessionCallbackRecordMap) {
1531                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1532                     if (record == null) {
1533                         Log.e(TAG, "Callback not found for seq " + seq);
1534                         return;
1535                     }
1536                     record.postVideoUnavailable(reason);
1537                 }
1538             }
1539 
1540             @Override
1541             public void onContentAllowed(int seq) {
1542                 synchronized (mSessionCallbackRecordMap) {
1543                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1544                     if (record == null) {
1545                         Log.e(TAG, "Callback not found for seq " + seq);
1546                         return;
1547                     }
1548                     record.postContentAllowed();
1549                 }
1550             }
1551 
1552             @Override
1553             public void onContentBlocked(String rating, int seq) {
1554                 synchronized (mSessionCallbackRecordMap) {
1555                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1556                     if (record == null) {
1557                         Log.e(TAG, "Callback not found for seq " + seq);
1558                         return;
1559                     }
1560                     record.postContentBlocked(TvContentRating.unflattenFromString(rating));
1561                 }
1562             }
1563 
1564             @Override
1565             public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
1566                 synchronized (mSessionCallbackRecordMap) {
1567                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1568                     if (record == null) {
1569                         Log.e(TAG, "Callback not found for seq " + seq);
1570                         return;
1571                     }
1572                     record.postLayoutSurface(left, top, right, bottom);
1573                 }
1574             }
1575 
1576             @Override
1577             public void onSessionEvent(String eventType, Bundle eventArgs, int seq) {
1578                 synchronized (mSessionCallbackRecordMap) {
1579                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1580                     if (record == null) {
1581                         Log.e(TAG, "Callback not found for seq " + seq);
1582                         return;
1583                     }
1584                     record.postSessionEvent(eventType, eventArgs);
1585                 }
1586             }
1587 
1588             @Override
1589             public void onTimeShiftStatusChanged(int status, int seq) {
1590                 synchronized (mSessionCallbackRecordMap) {
1591                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1592                     if (record == null) {
1593                         Log.e(TAG, "Callback not found for seq " + seq);
1594                         return;
1595                     }
1596                     record.postTimeShiftStatusChanged(status);
1597                 }
1598             }
1599 
1600             @Override
1601             public void onTimeShiftStartPositionChanged(long timeMs, int seq) {
1602                 synchronized (mSessionCallbackRecordMap) {
1603                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1604                     if (record == null) {
1605                         Log.e(TAG, "Callback not found for seq " + seq);
1606                         return;
1607                     }
1608                     record.postTimeShiftStartPositionChanged(timeMs);
1609                 }
1610             }
1611 
1612             @Override
1613             public void onTimeShiftCurrentPositionChanged(long timeMs, int seq) {
1614                 synchronized (mSessionCallbackRecordMap) {
1615                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1616                     if (record == null) {
1617                         Log.e(TAG, "Callback not found for seq " + seq);
1618                         return;
1619                     }
1620                     record.postTimeShiftCurrentPositionChanged(timeMs);
1621                 }
1622             }
1623 
1624             @Override
1625             public void onAitInfoUpdated(AitInfo aitInfo, int seq) {
1626                 synchronized (mSessionCallbackRecordMap) {
1627                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1628                     if (record == null) {
1629                         Log.e(TAG, "Callback not found for seq " + seq);
1630                         return;
1631                     }
1632                     record.postAitInfoUpdated(aitInfo);
1633                 }
1634             }
1635 
1636             @Override
1637             public void onSignalStrength(int strength, int seq) {
1638                 synchronized (mSessionCallbackRecordMap) {
1639                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1640                     if (record == null) {
1641                         Log.e(TAG, "Callback not found for seq " + seq);
1642                         return;
1643                     }
1644                     record.postSignalStrength(strength);
1645                 }
1646             }
1647 
1648             @Override
1649             public void onCueingMessageAvailability(boolean available, int seq) {
1650                 synchronized (mSessionCallbackRecordMap) {
1651                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1652                     if (record == null) {
1653                         Log.e(TAG, "Callback not found for seq " + seq);
1654                         return;
1655                     }
1656                     record.postCueingMessageAvailability(available);
1657                 }
1658             }
1659 
1660             @Override
1661             public void onTimeShiftMode(int mode, int seq) {
1662                 synchronized (mSessionCallbackRecordMap) {
1663                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1664                     if (record == null) {
1665                         Log.e(TAG, "Callback not found for seq " + seq);
1666                         return;
1667                     }
1668                     record.postTimeShiftMode(mode);
1669                 }
1670             }
1671 
1672             @Override
1673             public void onAvailableSpeeds(float[] speeds, int seq) {
1674                 synchronized (mSessionCallbackRecordMap) {
1675                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1676                     if (record == null) {
1677                         Log.e(TAG, "Callback not found for seq " + seq);
1678                         return;
1679                     }
1680                     record.postAvailableSpeeds(speeds);
1681                 }
1682             }
1683 
1684             @Override
1685             public void onTuned(Uri channelUri, int seq) {
1686                 synchronized (mSessionCallbackRecordMap) {
1687                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1688                     if (record == null) {
1689                         Log.e(TAG, "Callback not found for seq " + seq);
1690                         return;
1691                     }
1692                     record.postTuned(channelUri);
1693                     // TODO: synchronized and wrap the channelUri
1694                 }
1695             }
1696 
1697             @Override
1698             public void onTvMessage(int type, Bundle data, int seq) {
1699                 synchronized (mSessionCallbackRecordMap) {
1700                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1701                     if (record == null) {
1702                         Log.e(TAG, "Callback not found for seq " + seq);
1703                         return;
1704                     }
1705                     record.postTvMessage(type, data);
1706                 }
1707             }
1708 
1709             @Override
1710             public void onRecordingStopped(Uri recordedProgramUri, int seq) {
1711                 synchronized (mSessionCallbackRecordMap) {
1712                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1713                     if (record == null) {
1714                         Log.e(TAG, "Callback not found for seq " + seq);
1715                         return;
1716                     }
1717                     record.postRecordingStopped(recordedProgramUri);
1718                 }
1719             }
1720 
1721             @Override
1722             public void onError(int error, int seq) {
1723                 synchronized (mSessionCallbackRecordMap) {
1724                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1725                     if (record == null) {
1726                         Log.e(TAG, "Callback not found for seq " + seq);
1727                         return;
1728                     }
1729                     record.postError(error);
1730                 }
1731             }
1732 
1733             @Override
1734             public void onBroadcastInfoResponse(BroadcastInfoResponse response, int seq) {
1735                 synchronized (mSessionCallbackRecordMap) {
1736                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1737                     if (record == null) {
1738                         Log.e(TAG, "Callback not found for seq " + seq);
1739                         return;
1740                     }
1741                     record.postBroadcastInfoResponse(response);
1742                 }
1743             }
1744 
1745             @Override
1746             public void onAdResponse(AdResponse response, int seq) {
1747                 synchronized (mSessionCallbackRecordMap) {
1748                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1749                     if (record == null) {
1750                         Log.e(TAG, "Callback not found for seq " + seq);
1751                         return;
1752                     }
1753                     record.postAdResponse(response);
1754                 }
1755             }
1756 
1757             @Override
1758             public void onAdBufferConsumed(AdBuffer buffer, int seq) {
1759                 synchronized (mSessionCallbackRecordMap) {
1760                     SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
1761                     if (record == null) {
1762                         Log.e(TAG, "Callback not found for seq " + seq);
1763                         return;
1764                     }
1765                     record.postAdBufferConsumed(buffer);
1766                 }
1767             }
1768         };
1769         ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
1770             @Override
1771             public void onInputAdded(String inputId) {
1772                 synchronized (mLock) {
1773                     mStateMap.put(inputId, INPUT_STATE_CONNECTED);
1774                     for (TvInputCallbackRecord record : mCallbackRecords) {
1775                         record.postInputAdded(inputId);
1776                     }
1777                 }
1778             }
1779 
1780             @Override
1781             public void onInputRemoved(String inputId) {
1782                 synchronized (mLock) {
1783                     mStateMap.remove(inputId);
1784                     for (TvInputCallbackRecord record : mCallbackRecords) {
1785                         record.postInputRemoved(inputId);
1786                     }
1787                 }
1788             }
1789 
1790             @Override
1791             public void onInputUpdated(String inputId) {
1792                 synchronized (mLock) {
1793                     for (TvInputCallbackRecord record : mCallbackRecords) {
1794                         record.postInputUpdated(inputId);
1795                     }
1796                 }
1797             }
1798 
1799             @Override
1800             public void onInputStateChanged(String inputId, int state) {
1801                 synchronized (mLock) {
1802                     mStateMap.put(inputId, state);
1803                     for (TvInputCallbackRecord record : mCallbackRecords) {
1804                         record.postInputStateChanged(inputId, state);
1805                     }
1806                 }
1807             }
1808 
1809             @Override
1810             public void onTvInputInfoUpdated(TvInputInfo inputInfo) {
1811                 synchronized (mLock) {
1812                     for (TvInputCallbackRecord record : mCallbackRecords) {
1813                         record.postTvInputInfoUpdated(inputInfo);
1814                     }
1815                 }
1816             }
1817 
1818             @Override
1819             public void onCurrentTunedInfosUpdated(List<TunedInfo> currentTunedInfos) {
1820                 synchronized (mLock) {
1821                     for (TvInputCallbackRecord record : mCallbackRecords) {
1822                         record.postCurrentTunedInfosUpdated(currentTunedInfos);
1823                     }
1824                 }
1825             }
1826         };
1827         try {
1828             if (mService != null) {
1829                 mService.registerCallback(managerCallback, mUserId);
1830                 List<TvInputInfo> infos = mService.getTvInputList(mUserId);
1831                 synchronized (mLock) {
1832                     for (TvInputInfo info : infos) {
1833                         String inputId = info.getId();
1834                         mStateMap.put(inputId, mService.getTvInputState(inputId, mUserId));
1835                     }
1836                 }
1837             }
1838         } catch (RemoteException e) {
1839             throw e.rethrowFromSystemServer();
1840         }
1841     }
1842 
1843     /**
1844      * Returns the complete list of TV inputs on the system.
1845      *
1846      * @return List of {@link TvInputInfo} for each TV input that describes its meta information.
1847      */
getTvInputList()1848     public List<TvInputInfo> getTvInputList() {
1849         try {
1850             return mService.getTvInputList(mUserId);
1851         } catch (RemoteException e) {
1852             throw e.rethrowFromSystemServer();
1853         }
1854     }
1855 
1856     /**
1857      * Returns the {@link TvInputInfo} for a given TV input.
1858      *
1859      * @param inputId The ID of the TV input.
1860      * @return the {@link TvInputInfo} for a given TV input. {@code null} if not found.
1861      */
1862     @Nullable
getTvInputInfo(@onNull String inputId)1863     public TvInputInfo getTvInputInfo(@NonNull String inputId) {
1864         Preconditions.checkNotNull(inputId);
1865         try {
1866             return mService.getTvInputInfo(inputId, mUserId);
1867         } catch (RemoteException e) {
1868             throw e.rethrowFromSystemServer();
1869         }
1870     }
1871 
1872     /**
1873      * Updates the <code>TvInputInfo</code> for an existing TV input. A TV input service
1874      * implementation may call this method to pass the application and system an up-to-date
1875      * <code>TvInputInfo</code> object that describes itself.
1876      *
1877      * <p>The system automatically creates a <code>TvInputInfo</code> object for each TV input,
1878      * based on the information collected from the <code>AndroidManifest.xml</code>, thus it is not
1879      * necessary to call this method unless such information has changed dynamically.
1880      * Use {@link TvInputInfo.Builder} to build a new <code>TvInputInfo</code> object.
1881      *
1882      * <p>Attempting to change information about a TV input that the calling package does not own
1883      * does nothing.
1884      *
1885      * @param inputInfo The <code>TvInputInfo</code> object that contains new information.
1886      * @throws IllegalArgumentException if the argument is {@code null}.
1887      * @see TvInputCallback#onTvInputInfoUpdated(TvInputInfo)
1888      */
updateTvInputInfo(@onNull TvInputInfo inputInfo)1889     public void updateTvInputInfo(@NonNull TvInputInfo inputInfo) {
1890         Preconditions.checkNotNull(inputInfo);
1891         try {
1892             mService.updateTvInputInfo(inputInfo, mUserId);
1893         } catch (RemoteException e) {
1894             throw e.rethrowFromSystemServer();
1895         }
1896     }
1897 
1898     /**
1899      * Returns the state of a given TV input.
1900      *
1901      * <p>The state is one of the following:
1902      * <ul>
1903      * <li>{@link #INPUT_STATE_CONNECTED}
1904      * <li>{@link #INPUT_STATE_CONNECTED_STANDBY}
1905      * <li>{@link #INPUT_STATE_DISCONNECTED}
1906      * </ul>
1907      *
1908      * @param inputId The ID of the TV input.
1909      * @throws IllegalArgumentException if the argument is {@code null}.
1910      */
1911     @InputState
getInputState(@onNull String inputId)1912     public int getInputState(@NonNull String inputId) {
1913         Preconditions.checkNotNull(inputId);
1914         synchronized (mLock) {
1915             Integer state = mStateMap.get(inputId);
1916             if (state == null) {
1917                 Log.w(TAG, "Unrecognized input ID: " + inputId);
1918                 return INPUT_STATE_DISCONNECTED;
1919             }
1920             return state;
1921         }
1922     }
1923 
1924     /**
1925      * Returns available extension interfaces of a given hardware TV input. This can be used to
1926      * provide domain-specific features that are only known between certain hardware TV inputs
1927      * and their clients.
1928      *
1929      * @param inputId The ID of the TV input.
1930      * @return a non-null list of extension interface names available to the caller. An empty
1931      *         list indicates the given TV input is not found, or the given TV input is not a
1932      *         hardware TV input, or the given TV input doesn't support any extension
1933      *         interfaces, or the caller doesn't hold the required permission for the extension
1934      *         interfaces supported by the given TV input.
1935      * @see #getExtensionInterface
1936      * @hide
1937      */
1938     @SystemApi
1939     @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE)
1940     @NonNull
getAvailableExtensionInterfaceNames(@onNull String inputId)1941     public List<String> getAvailableExtensionInterfaceNames(@NonNull String inputId) {
1942         Preconditions.checkNotNull(inputId);
1943         try {
1944             return mService.getAvailableExtensionInterfaceNames(inputId, mUserId);
1945         } catch (RemoteException e) {
1946             throw e.rethrowFromSystemServer();
1947         }
1948     }
1949 
1950     /**
1951      * Returns an extension interface of a given hardware TV input. This can be used to provide
1952      * domain-specific features that are only known between certain hardware TV inputs and
1953      * their clients.
1954      *
1955      * @param inputId The ID of the TV input.
1956      * @param name The extension interface name.
1957      * @return an {@link IBinder} for the given extension interface, {@code null} if the given TV
1958      *         input is not found, or if the given TV input is not a hardware TV input, or if the
1959      *         given TV input doesn't support the given extension interface, or if the caller
1960      *         doesn't hold the required permission for the given extension interface.
1961      * @see #getAvailableExtensionInterfaceNames
1962      * @hide
1963      */
1964     @SystemApi
1965     @RequiresPermission(android.Manifest.permission.TIS_EXTENSION_INTERFACE)
1966     @Nullable
getExtensionInterface(@onNull String inputId, @NonNull String name)1967     public IBinder getExtensionInterface(@NonNull String inputId, @NonNull String name) {
1968         Preconditions.checkNotNull(inputId);
1969         Preconditions.checkNotNull(name);
1970         try {
1971             return mService.getExtensionInterface(inputId, name, mUserId);
1972         } catch (RemoteException e) {
1973             throw e.rethrowFromSystemServer();
1974         }
1975     }
1976 
1977     /**
1978      * Registers a {@link TvInputCallback}.
1979      *
1980      * @param callback A callback used to monitor status of the TV inputs.
1981      * @param handler A {@link Handler} that the status change will be delivered to.
1982      */
registerCallback(@onNull TvInputCallback callback, @NonNull Handler handler)1983     public void registerCallback(@NonNull TvInputCallback callback, @NonNull Handler handler) {
1984         Preconditions.checkNotNull(callback);
1985         Preconditions.checkNotNull(handler);
1986         synchronized (mLock) {
1987             mCallbackRecords.add(new TvInputCallbackRecord(callback, handler));
1988         }
1989     }
1990 
1991     /**
1992      * Unregisters the existing {@link TvInputCallback}.
1993      *
1994      * @param callback The existing callback to remove.
1995      */
unregisterCallback(@onNull final TvInputCallback callback)1996     public void unregisterCallback(@NonNull final TvInputCallback callback) {
1997         Preconditions.checkNotNull(callback);
1998         synchronized (mLock) {
1999             for (Iterator<TvInputCallbackRecord> it = mCallbackRecords.iterator();
2000                     it.hasNext(); ) {
2001                 TvInputCallbackRecord record = it.next();
2002                 if (record.getCallback() == callback) {
2003                     it.remove();
2004                     break;
2005                 }
2006             }
2007         }
2008     }
2009 
2010     /**
2011      * Returns the user's parental controls enabled state.
2012      *
2013      * @return {@code true} if the user enabled the parental controls, {@code false} otherwise.
2014      */
isParentalControlsEnabled()2015     public boolean isParentalControlsEnabled() {
2016         try {
2017             return mService.isParentalControlsEnabled(mUserId);
2018         } catch (RemoteException e) {
2019             throw e.rethrowFromSystemServer();
2020         }
2021     }
2022 
2023     /**
2024      * Sets the user's parental controls enabled state.
2025      *
2026      * @param enabled The user's parental controls enabled state. {@code true} if the user enabled
2027      *            the parental controls, {@code false} otherwise.
2028      * @see #isParentalControlsEnabled
2029      * @hide
2030      */
2031     @SystemApi
2032     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
setParentalControlsEnabled(boolean enabled)2033     public void setParentalControlsEnabled(boolean enabled) {
2034         try {
2035             mService.setParentalControlsEnabled(enabled, mUserId);
2036         } catch (RemoteException e) {
2037             throw e.rethrowFromSystemServer();
2038         }
2039     }
2040 
2041     /**
2042      * Checks whether a given TV content rating is blocked by the user.
2043      *
2044      * @param rating The TV content rating to check. Can be {@link TvContentRating#UNRATED}.
2045      * @return {@code true} if the given TV content rating is blocked, {@code false} otherwise.
2046      */
isRatingBlocked(@onNull TvContentRating rating)2047     public boolean isRatingBlocked(@NonNull TvContentRating rating) {
2048         Preconditions.checkNotNull(rating);
2049         try {
2050             return mService.isRatingBlocked(rating.flattenToString(), mUserId);
2051         } catch (RemoteException e) {
2052             throw e.rethrowFromSystemServer();
2053         }
2054     }
2055 
2056     /**
2057      * Returns the list of blocked content ratings.
2058      *
2059      * @return the list of content ratings blocked by the user.
2060      */
getBlockedRatings()2061     public List<TvContentRating> getBlockedRatings() {
2062         try {
2063             List<TvContentRating> ratings = new ArrayList<>();
2064             for (String rating : mService.getBlockedRatings(mUserId)) {
2065                 ratings.add(TvContentRating.unflattenFromString(rating));
2066             }
2067             return ratings;
2068         } catch (RemoteException e) {
2069             throw e.rethrowFromSystemServer();
2070         }
2071     }
2072 
2073     /**
2074      * Adds a user blocked content rating.
2075      *
2076      * @param rating The content rating to block.
2077      * @see #isRatingBlocked
2078      * @see #removeBlockedRating
2079      * @hide
2080      */
2081     @SystemApi
2082     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
addBlockedRating(@onNull TvContentRating rating)2083     public void addBlockedRating(@NonNull TvContentRating rating) {
2084         Preconditions.checkNotNull(rating);
2085         try {
2086             mService.addBlockedRating(rating.flattenToString(), mUserId);
2087         } catch (RemoteException e) {
2088             throw e.rethrowFromSystemServer();
2089         }
2090     }
2091 
2092     /**
2093      * Removes a user blocked content rating.
2094      *
2095      * @param rating The content rating to unblock.
2096      * @see #isRatingBlocked
2097      * @see #addBlockedRating
2098      * @hide
2099      */
2100     @SystemApi
2101     @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS)
removeBlockedRating(@onNull TvContentRating rating)2102     public void removeBlockedRating(@NonNull TvContentRating rating) {
2103         Preconditions.checkNotNull(rating);
2104         try {
2105             mService.removeBlockedRating(rating.flattenToString(), mUserId);
2106         } catch (RemoteException e) {
2107             throw e.rethrowFromSystemServer();
2108         }
2109     }
2110 
2111     /**
2112      * Returns the list of all TV content rating systems defined.
2113      * @hide
2114      */
2115     @SystemApi
2116     @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS)
getTvContentRatingSystemList()2117     public List<TvContentRatingSystemInfo> getTvContentRatingSystemList() {
2118         try {
2119             return mService.getTvContentRatingSystemList(mUserId);
2120         } catch (RemoteException e) {
2121             throw e.rethrowFromSystemServer();
2122         }
2123     }
2124 
2125     /**
2126      * Notifies the TV input of the given preview program that the program's browsable state is
2127      * disabled.
2128      * @hide
2129      */
2130     @SystemApi
2131     @RequiresPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)
notifyPreviewProgramBrowsableDisabled(String packageName, long programId)2132     public void notifyPreviewProgramBrowsableDisabled(String packageName, long programId) {
2133         Intent intent = new Intent();
2134         intent.setAction(TvContract.ACTION_PREVIEW_PROGRAM_BROWSABLE_DISABLED);
2135         intent.putExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, programId);
2136         intent.setPackage(packageName);
2137         try {
2138             mService.sendTvInputNotifyIntent(intent, mUserId);
2139         } catch (RemoteException e) {
2140             throw e.rethrowFromSystemServer();
2141         }
2142     }
2143 
2144     /**
2145      * Notifies the TV input of the given watch next program that the program's browsable state is
2146      * disabled.
2147      * @hide
2148      */
2149     @SystemApi
2150     @RequiresPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)
notifyWatchNextProgramBrowsableDisabled(String packageName, long programId)2151     public void notifyWatchNextProgramBrowsableDisabled(String packageName, long programId) {
2152         Intent intent = new Intent();
2153         intent.setAction(TvContract.ACTION_WATCH_NEXT_PROGRAM_BROWSABLE_DISABLED);
2154         intent.putExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, programId);
2155         intent.setPackage(packageName);
2156         try {
2157             mService.sendTvInputNotifyIntent(intent, mUserId);
2158         } catch (RemoteException e) {
2159             throw e.rethrowFromSystemServer();
2160         }
2161     }
2162 
2163     /**
2164      * Notifies the TV input of the given preview program that the program is added to watch next.
2165      * @hide
2166      */
2167     @SystemApi
2168     @RequiresPermission(android.Manifest.permission.NOTIFY_TV_INPUTS)
notifyPreviewProgramAddedToWatchNext(String packageName, long previewProgramId, long watchNextProgramId)2169     public void notifyPreviewProgramAddedToWatchNext(String packageName, long previewProgramId,
2170             long watchNextProgramId) {
2171         Intent intent = new Intent();
2172         intent.setAction(TvContract.ACTION_PREVIEW_PROGRAM_ADDED_TO_WATCH_NEXT);
2173         intent.putExtra(TvContract.EXTRA_PREVIEW_PROGRAM_ID, previewProgramId);
2174         intent.putExtra(TvContract.EXTRA_WATCH_NEXT_PROGRAM_ID, watchNextProgramId);
2175         intent.setPackage(packageName);
2176         try {
2177             mService.sendTvInputNotifyIntent(intent, mUserId);
2178         } catch (RemoteException e) {
2179             throw e.rethrowFromSystemServer();
2180         }
2181     }
2182 
2183     /**
2184      * Creates a {@link Session} for a given TV input.
2185      *
2186      * <p>The number of sessions that can be created at the same time is limited by the capability
2187      * of the given TV input.
2188      *
2189      * @param inputId The ID of the TV input.
2190      * @param tvAppAttributionSource The Attribution Source of the TV App.
2191      * @param callback A callback used to receive the created session.
2192      * @param handler A {@link Handler} that the session creation will be delivered to.
2193      * @hide
2194      */
createSession(@onNull String inputId, @NonNull AttributionSource tvAppAttributionSource, @NonNull final SessionCallback callback, @NonNull Handler handler)2195     public void createSession(@NonNull String inputId,
2196             @NonNull AttributionSource tvAppAttributionSource,
2197             @NonNull final SessionCallback callback, @NonNull Handler handler) {
2198         createSessionInternal(inputId, tvAppAttributionSource, false, callback, handler);
2199     }
2200 
2201     /**
2202      * Get a the client pid when creating the session with the session id provided.
2203      *
2204      * @param sessionId a String of session id that is used to query the client pid.
2205      * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID}
2206      *         if the call fails.
2207      *
2208      * @hide
2209      */
2210     @SystemApi
2211     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
getClientPid(@onNull String sessionId)2212     public int getClientPid(@NonNull String sessionId) {
2213         return getClientPidInternal(sessionId);
2214     };
2215 
2216     /**
2217      * Returns a priority for the given use case type and the client's foreground or background
2218      * status.
2219      *
2220      * @param useCase the use case type of the client.
2221      *        {@see TvInputService#PriorityHintUseCaseType}.
2222      * @param sessionId the unique id of the session owned by the client.
2223      *        {@see TvInputService#onCreateSession(String, String, AttributionSource)}.
2224      *
2225      * @return the use case priority value for the given use case type and the client's foreground
2226      *         or background status.
2227      *
2228      * @hide
2229      */
2230     @SystemApi
2231     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
getClientPriority(@vInputService.PriorityHintUseCaseType int useCase, @NonNull String sessionId)2232     public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase,
2233             @NonNull String sessionId) {
2234         Preconditions.checkNotNull(sessionId);
2235         if (!isValidUseCase(useCase)) {
2236             throw new IllegalArgumentException("Invalid use case: " + useCase);
2237         }
2238         return getClientPriorityInternal(useCase, sessionId);
2239     };
2240 
2241     /**
2242      * Returns a priority for the given use case type and the caller's foreground or background
2243      * status.
2244      *
2245      * @param useCase the use case type of the caller.
2246      *        {@see TvInputService#PriorityHintUseCaseType}.
2247      *
2248      * @return the use case priority value for the given use case type and the caller's foreground
2249      *         or background status.
2250      *
2251      * @hide
2252      */
2253     @SystemApi
2254     @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
getClientPriority(@vInputService.PriorityHintUseCaseType int useCase)2255     public int getClientPriority(@TvInputService.PriorityHintUseCaseType int useCase) {
2256         if (!isValidUseCase(useCase)) {
2257             throw new IllegalArgumentException("Invalid use case: " + useCase);
2258         }
2259         return getClientPriorityInternal(useCase, null);
2260     };
2261     /**
2262      * Creates a recording {@link Session} for a given TV input.
2263      *
2264      * <p>The number of sessions that can be created at the same time is limited by the capability
2265      * of the given TV input.
2266      *
2267      * @param inputId The ID of the TV input.
2268      * @param callback A callback used to receive the created session.
2269      * @param handler A {@link Handler} that the session creation will be delivered to.
2270      * @hide
2271      */
createRecordingSession(@onNull String inputId, @NonNull final SessionCallback callback, @NonNull Handler handler)2272     public void createRecordingSession(@NonNull String inputId,
2273             @NonNull final SessionCallback callback, @NonNull Handler handler) {
2274         createSessionInternal(inputId, null, true, callback, handler);
2275     }
2276 
createSessionInternal(String inputId, AttributionSource tvAppAttributionSource, boolean isRecordingSession, SessionCallback callback, Handler handler)2277     private void createSessionInternal(String inputId, AttributionSource tvAppAttributionSource,
2278             boolean isRecordingSession, SessionCallback callback, Handler handler) {
2279         Preconditions.checkNotNull(inputId);
2280         Preconditions.checkNotNull(callback);
2281         Preconditions.checkNotNull(handler);
2282         SessionCallbackRecord record = new SessionCallbackRecord(callback, handler);
2283         synchronized (mSessionCallbackRecordMap) {
2284             int seq = mNextSeq++;
2285             mSessionCallbackRecordMap.put(seq, record);
2286             try {
2287                 mService.createSession(
2288                         mClient, inputId, tvAppAttributionSource, isRecordingSession, seq, mUserId);
2289             } catch (RemoteException e) {
2290                 throw e.rethrowFromSystemServer();
2291             }
2292         }
2293     }
2294 
getClientPidInternal(String sessionId)2295     private int getClientPidInternal(String sessionId) {
2296         Preconditions.checkNotNull(sessionId);
2297         int clientPid = UNKNOWN_CLIENT_PID;
2298         try {
2299             clientPid = mService.getClientPid(sessionId);
2300         } catch (RemoteException e) {
2301             throw e.rethrowFromSystemServer();
2302         }
2303         return clientPid;
2304     }
2305 
getClientPriorityInternal(int useCase, String sessionId)2306     private int getClientPriorityInternal(int useCase, String sessionId) {
2307         try {
2308             return mService.getClientPriority(useCase, sessionId);
2309         } catch (RemoteException e) {
2310             throw e.rethrowFromSystemServer();
2311         }
2312     }
2313 
isValidUseCase(int useCase)2314     private boolean isValidUseCase(int useCase) {
2315         return useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND
2316             || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_SCAN
2317             || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK
2318             || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE
2319             || useCase == TvInputService.PRIORITY_HINT_USE_CASE_TYPE_RECORD;
2320     }
2321 
2322     /**
2323      * Returns the TvStreamConfig list of the given TV input.
2324      *
2325      * If you are using {@link Hardware} object from {@link
2326      * #acquireTvInputHardware}, you should get the list of available streams
2327      * from {@link HardwareCallback#onStreamConfigChanged} method, not from
2328      * here. This method is designed to be used with {@link #captureFrame} in
2329      * capture scenarios specifically and not suitable for any other use.
2330      *
2331      * @param inputId The ID of the TV input.
2332      * @return List of {@link TvStreamConfig} which is available for capturing
2333      *   of the given TV input.
2334      * @hide
2335      */
2336     @SystemApi
2337     @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT)
getAvailableTvStreamConfigList(String inputId)2338     public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId) {
2339         try {
2340             return mService.getAvailableTvStreamConfigList(inputId, mUserId);
2341         } catch (RemoteException e) {
2342             throw e.rethrowFromSystemServer();
2343         }
2344     }
2345 
2346     /**
2347      * Take a snapshot of the given TV input into the provided Surface.
2348      *
2349      * @param inputId The ID of the TV input.
2350      * @param surface the {@link Surface} to which the snapshot is captured.
2351      * @param config the {@link TvStreamConfig} which is used for capturing.
2352      * @return true when the {@link Surface} is ready to be captured.
2353      * @hide
2354      */
2355     @SystemApi
2356     @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT)
captureFrame(String inputId, Surface surface, TvStreamConfig config)2357     public boolean captureFrame(String inputId, Surface surface, TvStreamConfig config) {
2358         try {
2359             return mService.captureFrame(inputId, surface, config, mUserId);
2360         } catch (RemoteException e) {
2361             throw e.rethrowFromSystemServer();
2362         }
2363     }
2364 
2365     /**
2366      * Returns true if there is only a single TV input session.
2367      *
2368      * @hide
2369      */
2370     @SystemApi
2371     @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT)
isSingleSessionActive()2372     public boolean isSingleSessionActive() {
2373         try {
2374             return mService.isSingleSessionActive(mUserId);
2375         } catch (RemoteException e) {
2376             throw e.rethrowFromSystemServer();
2377         }
2378     }
2379 
2380     /**
2381      * Returns a list of TvInputHardwareInfo objects representing available hardware.
2382      *
2383      * @hide
2384      */
2385     @SystemApi
2386     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
getHardwareList()2387     public List<TvInputHardwareInfo> getHardwareList() {
2388         try {
2389             return mService.getHardwareList();
2390         } catch (RemoteException e) {
2391             throw e.rethrowFromSystemServer();
2392         }
2393     }
2394 
2395     /**
2396      * Acquires {@link Hardware} object for the given device ID.
2397      *
2398      * <p>A subsequent call to this method on the same {@code deviceId} will release the currently
2399      * acquired Hardware.
2400      *
2401      * @param deviceId The device ID to acquire Hardware for.
2402      * @param callback A callback to receive updates on Hardware.
2403      * @param info The TV input which will use the acquired Hardware.
2404      * @return Hardware on success, {@code null} otherwise.
2405      *
2406      * @hide
2407      * @removed
2408      */
2409     @SystemApi
2410     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
acquireTvInputHardware(int deviceId, final HardwareCallback callback, TvInputInfo info)2411     public Hardware acquireTvInputHardware(int deviceId, final HardwareCallback callback,
2412             TvInputInfo info) {
2413         return acquireTvInputHardware(deviceId, info, callback);
2414     }
2415 
2416     /**
2417      * Acquires {@link Hardware} object for the given device ID.
2418      *
2419      * <p>A subsequent call to this method on the same {@code deviceId} could release the currently
2420      * acquired Hardware if TunerResourceManager(TRM) detects higher priority from the current
2421      * request.
2422      *
2423      * <p>If the client would like to provide information for the TRM to compare, use
2424      * {@link #acquireTvInputHardware(int, TvInputInfo, HardwareCallback, String, int)} instead.
2425      *
2426      * <p>Otherwise default priority will be applied.
2427      *
2428      * @param deviceId The device ID to acquire Hardware for.
2429      * @param info The TV input which will use the acquired Hardware.
2430      * @param callback A callback to receive updates on Hardware.
2431      * @return Hardware on success, {@code null} otherwise.
2432      *
2433      * @hide
2434      */
2435     @SystemApi
2436     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
acquireTvInputHardware(int deviceId, @NonNull TvInputInfo info, @NonNull final HardwareCallback callback)2437     public Hardware acquireTvInputHardware(int deviceId, @NonNull TvInputInfo info,
2438             @NonNull final HardwareCallback callback) {
2439         Preconditions.checkNotNull(info);
2440         Preconditions.checkNotNull(callback);
2441         return acquireTvInputHardwareInternal(deviceId, info, null,
2442                 TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, new Executor() {
2443                     public void execute(Runnable r) {
2444                         r.run();
2445                     }
2446                 }, callback);
2447     }
2448 
2449     /**
2450      * Acquires {@link Hardware} object for the given device ID.
2451      *
2452      * <p>A subsequent call to this method on the same {@code deviceId} could release the currently
2453      * acquired Hardware if TunerResourceManager(TRM) detects higher priority from the current
2454      * request.
2455      *
2456      * @param deviceId The device ID to acquire Hardware for.
2457      * @param info The TV input which will use the acquired Hardware.
2458      * @param tvInputSessionId a String returned to TIS when the session was created.
2459      *        {@see TvInputService#onCreateSession(String, String, AttributionSource)}. If null, the
2460      *        client will be treated as a background app.
2461      * @param priorityHint The use case of the client. {@see TvInputService#PriorityHintUseCaseType}
2462      * @param executor the executor on which the listener would be invoked.
2463      * @param callback A callback to receive updates on Hardware.
2464      * @return Hardware on success, {@code null} otherwise. When the TRM decides to not grant
2465      *         resource, null is returned and the {@link IllegalStateException} is thrown with
2466      *         "No enough resources".
2467      *
2468      * @hide
2469      */
2470     @SystemApi
2471     @Nullable
2472     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2473     public Hardware acquireTvInputHardware(int deviceId, @NonNull TvInputInfo info,
2474             @Nullable String tvInputSessionId,
2475             @TvInputService.PriorityHintUseCaseType int priorityHint,
2476             @NonNull @CallbackExecutor Executor executor,
2477             @NonNull final HardwareCallback callback) {
2478         Preconditions.checkNotNull(info);
2479         Preconditions.checkNotNull(callback);
2480         return acquireTvInputHardwareInternal(deviceId, info, tvInputSessionId, priorityHint,
2481                 executor, callback);
2482     }
2483 
2484     /**
2485      * API to add a hardware device in the TvInputHardwareManager for CTS testing
2486      * purpose.
2487      *
2488      * @param deviceId Id of the adding hardware device.
2489      *
2490      * @hide
2491      */
2492     @TestApi
2493     public void addHardwareDevice(int deviceId) {
2494         try {
2495             mService.addHardwareDevice(deviceId);
2496         } catch (RemoteException e) {
2497             throw e.rethrowFromSystemServer();
2498         }
2499     }
2500 
2501     /**
2502      * API to remove a hardware device in the TvInputHardwareManager for CTS testing
2503      * purpose.
2504      *
2505      * @param deviceId Id of the removing hardware device.
2506      *
2507      * @hide
2508      */
2509     @TestApi
2510     public void removeHardwareDevice(int deviceId) {
2511         try {
2512             mService.removeHardwareDevice(deviceId);
2513         } catch (RemoteException e) {
2514             throw e.rethrowFromSystemServer();
2515         }
2516     }
2517 
2518     private Hardware acquireTvInputHardwareInternal(int deviceId, TvInputInfo info,
2519             String tvInputSessionId, int priorityHint,
2520             Executor executor, final HardwareCallback callback) {
2521         try {
2522             ITvInputHardware hardware =
2523                     mService.acquireTvInputHardware(deviceId, new ITvInputHardwareCallback.Stub() {
2524                 @Override
2525                 public void onReleased() {
2526                             final long identity = Binder.clearCallingIdentity();
2527                             try {
2528                                 executor.execute(() -> callback.onReleased());
2529                             } finally {
2530                                 Binder.restoreCallingIdentity(identity);
2531                             }
2532                 }
2533 
2534                 @Override
2535                 public void onStreamConfigChanged(TvStreamConfig[] configs) {
2536                             final long identity = Binder.clearCallingIdentity();
2537                             try {
2538                                 executor.execute(() -> callback.onStreamConfigChanged(configs));
2539                             } finally {
2540                                 Binder.restoreCallingIdentity(identity);
2541                             }
2542                 }
2543                     }, info, mUserId, tvInputSessionId, priorityHint);
2544             if (hardware == null) {
2545                 return null;
2546             }
2547             return new Hardware(hardware);
2548         } catch (RemoteException e) {
2549             throw e.rethrowFromSystemServer();
2550         }
2551     }
2552 
2553     /**
2554      * Releases previously acquired hardware object.
2555      *
2556      * @param deviceId The device ID this Hardware was acquired for
2557      * @param hardware Hardware to release.
2558      *
2559      * @hide
2560      */
2561     @SystemApi
2562     @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE)
2563     public void releaseTvInputHardware(int deviceId, Hardware hardware) {
2564         try {
2565             mService.releaseTvInputHardware(deviceId, hardware.getInterface(), mUserId);
2566         } catch (RemoteException e) {
2567             throw e.rethrowFromSystemServer();
2568         }
2569     }
2570 
2571     /**
2572      * Returns the list of currently available DVB frontend devices on the system.
2573      *
2574      * @return the list of {@link DvbDeviceInfo} objects representing available DVB devices.
2575      * @hide
2576      */
2577     @SystemApi
2578     @RequiresPermission(android.Manifest.permission.DVB_DEVICE)
2579     @NonNull
2580     public List<DvbDeviceInfo> getDvbDeviceList() {
2581         try {
2582             return mService.getDvbDeviceList();
2583         } catch (RemoteException e) {
2584             throw e.rethrowFromSystemServer();
2585         }
2586     }
2587 
2588     /**
2589      * Returns a {@link ParcelFileDescriptor} of a specified DVB device of a given type for a given
2590      * {@link DvbDeviceInfo}.
2591      *
2592      * @param info A {@link DvbDeviceInfo} to open a DVB device.
2593      * @param deviceType A DVB device type.
2594      * @return a {@link ParcelFileDescriptor} of a specified DVB device for a given
2595      * {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo}
2596      * failed to open.
2597      * @throws IllegalArgumentException if {@code deviceType} is invalid or the device is not found.
2598 
2599      * @see <a href="https://www.linuxtv.org/docs/dvbapi/dvbapi.html">Linux DVB API v3</a>
2600      * @hide
2601      */
2602     @SystemApi
2603     @RequiresPermission(android.Manifest.permission.DVB_DEVICE)
2604     @Nullable
2605     public ParcelFileDescriptor openDvbDevice(@NonNull DvbDeviceInfo info,
2606             @DvbDeviceType int deviceType) {
2607         try {
2608             if (DVB_DEVICE_START > deviceType || DVB_DEVICE_END < deviceType) {
2609                 throw new IllegalArgumentException("Invalid DVB device: " + deviceType);
2610             }
2611             return mService.openDvbDevice(info, deviceType);
2612         } catch (RemoteException e) {
2613             throw e.rethrowFromSystemServer();
2614         }
2615     }
2616 
2617     /**
2618      * Requests to make a channel browsable.
2619      *
2620      * <p>Once called, the system will review the request and make the channel browsable based on
2621      * its policy. The first request from a package is guaranteed to be approved.
2622      *
2623      * @param channelUri The URI for the channel to be browsable.
2624      * @hide
2625      */
2626     public void requestChannelBrowsable(Uri channelUri) {
2627         try {
2628             mService.requestChannelBrowsable(channelUri, mUserId);
2629         } catch (RemoteException e) {
2630             throw e.rethrowFromSystemServer();
2631         }
2632     }
2633 
2634     /**
2635      * Returns the list of session information for {@link TvInputService.Session} that are
2636      * currently in use.
2637      * <p> Permission com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS is required to get
2638      * the channel URIs. If the permission is not granted,
2639      * {@link TunedInfo#getChannelUri()} returns {@code null}.
2640      * @hide
2641      */
2642     @SystemApi
2643     @RequiresPermission(android.Manifest.permission.ACCESS_TUNED_INFO)
2644     @NonNull
2645     public List<TunedInfo> getCurrentTunedInfos() {
2646         try {
2647             return mService.getCurrentTunedInfos(mUserId);
2648         } catch (RemoteException e) {
2649             throw e.rethrowFromSystemServer();
2650         }
2651     }
2652 
2653     /**
2654      * The Session provides the per-session functionality of TV inputs.
2655      * @hide
2656      */
2657     public static final class Session {
2658         static final int DISPATCH_IN_PROGRESS = -1;
2659         static final int DISPATCH_NOT_HANDLED = 0;
2660         static final int DISPATCH_HANDLED = 1;
2661 
2662         private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
2663 
2664         private final ITvInputManager mService;
2665         private final int mUserId;
2666         private final int mSeq;
2667 
2668         // For scheduling input event handling on the main thread. This also serves as a lock to
2669         // protect pending input events and the input channel.
2670         private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
2671 
2672         private final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
2673         private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
2674         private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
2675 
2676         private IBinder mToken;
2677         private TvInputEventSender mSender;
2678         private InputChannel mChannel;
2679 
2680         private final Object mMetadataLock = new Object();
2681         // @GuardedBy("mMetadataLock")
2682         private final List<AudioPresentation> mAudioPresentations = new ArrayList<>();
2683         // @GuardedBy("mMetadataLock")
2684         private final List<TvTrackInfo> mAudioTracks = new ArrayList<>();
2685         // @GuardedBy("mMetadataLock")
2686         private final List<TvTrackInfo> mVideoTracks = new ArrayList<>();
2687         // @GuardedBy("mMetadataLock")
2688         private final List<TvTrackInfo> mSubtitleTracks = new ArrayList<>();
2689         // @GuardedBy("mMetadataLock")
2690         private int mSelectedAudioProgramId = AudioPresentation.PROGRAM_ID_UNKNOWN;
2691         // @GuardedBy("mMetadataLock")
2692         private int mSelectedAudioPresentationId = AudioPresentation.PRESENTATION_ID_UNKNOWN;
2693         // @GuardedBy("mMetadataLock")
2694         private String mSelectedAudioTrackId;
2695         // @GuardedBy("mMetadataLock")
2696         private String mSelectedVideoTrackId;
2697         // @GuardedBy("mMetadataLock")
2698         private String mSelectedSubtitleTrackId;
2699         // @GuardedBy("mMetadataLock")
2700         private int mVideoWidth;
2701         // @GuardedBy("mMetadataLock")
2702         private int mVideoHeight;
2703 
2704         private TvInteractiveAppManager.Session mIAppSession;
2705         private boolean mIAppNotificationEnabled = false;
2706 
2707         private Session(IBinder token, InputChannel channel, ITvInputManager service, int userId,
2708                 int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
2709             mToken = token;
2710             mChannel = channel;
2711             mService = service;
2712             mUserId = userId;
2713             mSeq = seq;
2714             mSessionCallbackRecordMap = sessionCallbackRecordMap;
2715         }
2716 
2717         public TvInteractiveAppManager.Session getInteractiveAppSession() {
2718             return mIAppSession;
2719         }
2720 
2721         public void setInteractiveAppSession(TvInteractiveAppManager.Session iAppSession) {
2722             this.mIAppSession = iAppSession;
2723         }
2724 
2725         /**
2726          * Releases this session.
2727          */
2728         public void release() {
2729             if (mToken == null) {
2730                 Log.w(TAG, "The session has been already released");
2731                 return;
2732             }
2733             try {
2734                 mService.releaseSession(mToken, mUserId);
2735             } catch (RemoteException e) {
2736                 throw e.rethrowFromSystemServer();
2737             }
2738 
2739             releaseInternal();
2740         }
2741 
2742         /**
2743          * Sets this as the main session. The main session is a session whose corresponding TV
2744          * input determines the HDMI-CEC active source device.
2745          *
2746          * @see TvView#setMain
2747          */
2748         void setMain() {
2749             if (mToken == null) {
2750                 Log.w(TAG, "The session has been already released");
2751                 return;
2752             }
2753             try {
2754                 mService.setMainSession(mToken, mUserId);
2755             } catch (RemoteException e) {
2756                 throw e.rethrowFromSystemServer();
2757             }
2758         }
2759 
2760         /**
2761          * Sets the {@link android.view.Surface} for this session.
2762          *
2763          * @param surface A {@link android.view.Surface} used to render video.
2764          */
2765         public void setSurface(Surface surface) {
2766             if (mToken == null) {
2767                 Log.w(TAG, "The session has been already released");
2768                 return;
2769             }
2770             // surface can be null.
2771             try {
2772                 mService.setSurface(mToken, surface, mUserId);
2773             } catch (RemoteException e) {
2774                 throw e.rethrowFromSystemServer();
2775             }
2776         }
2777 
2778         /**
2779          * Notifies of any structural changes (format or size) of the surface passed in
2780          * {@link #setSurface}.
2781          *
2782          * @param format The new PixelFormat of the surface.
2783          * @param width The new width of the surface.
2784          * @param height The new height of the surface.
2785          */
2786         public void dispatchSurfaceChanged(int format, int width, int height) {
2787             if (mToken == null) {
2788                 Log.w(TAG, "The session has been already released");
2789                 return;
2790             }
2791             try {
2792                 mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
2793             } catch (RemoteException e) {
2794                 throw e.rethrowFromSystemServer();
2795             }
2796         }
2797 
2798         /**
2799          * Sets the relative stream volume of this session to handle a change of audio focus.
2800          *
2801          * @param volume A volume value between 0.0f to 1.0f.
2802          * @throws IllegalArgumentException if the volume value is out of range.
2803          */
2804         public void setStreamVolume(float volume) {
2805             if (mToken == null) {
2806                 Log.w(TAG, "The session has been already released");
2807                 return;
2808             }
2809             try {
2810                 if (volume < 0.0f || volume > 1.0f) {
2811                     throw new IllegalArgumentException("volume should be between 0.0f and 1.0f");
2812                 }
2813                 mService.setVolume(mToken, volume, mUserId);
2814             } catch (RemoteException e) {
2815                 throw e.rethrowFromSystemServer();
2816             }
2817         }
2818 
2819         /**
2820          * Tunes to a given channel.
2821          *
2822          * @param channelUri The URI of a channel.
2823          */
2824         public void tune(Uri channelUri) {
2825             tune(channelUri, null);
2826         }
2827 
2828         /**
2829          * Tunes to a given channel.
2830          *
2831          * @param channelUri The URI of a channel.
2832          * @param params A set of extra parameters which might be handled with this tune event.
2833          */
2834         public void tune(@NonNull Uri channelUri, Bundle params) {
2835             Preconditions.checkNotNull(channelUri);
2836             if (mToken == null) {
2837                 Log.w(TAG, "The session has been already released");
2838                 return;
2839             }
2840             synchronized (mMetadataLock) {
2841                 mAudioPresentations.clear();
2842                 mAudioTracks.clear();
2843                 mVideoTracks.clear();
2844                 mSubtitleTracks.clear();
2845                 mSelectedAudioProgramId = AudioPresentation.PROGRAM_ID_UNKNOWN;
2846                 mSelectedAudioPresentationId = AudioPresentation.PRESENTATION_ID_UNKNOWN;
2847                 mSelectedAudioTrackId = null;
2848                 mSelectedVideoTrackId = null;
2849                 mSelectedSubtitleTrackId = null;
2850                 mVideoWidth = 0;
2851                 mVideoHeight = 0;
2852             }
2853             try {
2854                 mService.tune(mToken, channelUri, params, mUserId);
2855             } catch (RemoteException e) {
2856                 throw e.rethrowFromSystemServer();
2857             }
2858         }
2859 
2860         /**
2861          * Enables or disables the caption for this session.
2862          *
2863          * @param enabled {@code true} to enable, {@code false} to disable.
2864          */
2865         public void setCaptionEnabled(boolean enabled) {
2866             if (mToken == null) {
2867                 Log.w(TAG, "The session has been already released");
2868                 return;
2869             }
2870             try {
2871                 mService.setCaptionEnabled(mToken, enabled, mUserId);
2872             } catch (RemoteException e) {
2873                 throw e.rethrowFromSystemServer();
2874             }
2875         }
2876 
2877         /**
2878          * Selects an audio presentation
2879          *
2880          * @param presentationId The ID of the audio presentation to select.
2881          * @param programId The ID of the program offering the selected audio presentation.
2882          * @see #getAudioPresentations
2883          */
2884         public void selectAudioPresentation(int presentationId, int programId) {
2885             synchronized (mMetadataLock) {
2886                 if (presentationId != AudioPresentation.PRESENTATION_ID_UNKNOWN
2887                         && !containsAudioPresentation(mAudioPresentations, presentationId)) {
2888                     Log.w(TAG, "Invalid audio presentation id: " + presentationId);
2889                     return;
2890                 }
2891             }
2892             if (mToken == null) {
2893                 Log.w(TAG, "The session has been already released");
2894                 return;
2895             }
2896             try {
2897                 mService.selectAudioPresentation(mToken, presentationId, programId, mUserId);
2898             } catch (RemoteException e) {
2899                 throw e.rethrowFromSystemServer();
2900             }
2901         }
2902 
2903         private boolean containsAudioPresentation(List<AudioPresentation> audioPresentations,
2904                     int presentationId) {
2905             synchronized (mMetadataLock) {
2906                 for (AudioPresentation audioPresentation : audioPresentations) {
2907                     if (audioPresentation.getPresentationId() == presentationId) {
2908                         return true;
2909                     }
2910                 }
2911                 return false;
2912             }
2913         }
2914 
2915         /**
2916          * Returns a list of audio presentations.
2917          *
2918          * @return the list of audio presentations.
2919          * Returns empty AudioPresentation list if no presentations are available.
2920          */
2921         public List<AudioPresentation> getAudioPresentations() {
2922             synchronized (mMetadataLock) {
2923                 if (mAudioPresentations == null) {
2924                     return new ArrayList<AudioPresentation>();
2925                 }
2926                 return new ArrayList<AudioPresentation>(mAudioPresentations);
2927             }
2928         }
2929 
2930         /**
2931          * Returns the program ID of the selected audio presentation.
2932          *
2933          * @return The ID of the program providing the selected audio presentation.
2934          * Returns {@value AudioPresentation.PROGRAM_ID_UNKNOWN} if no audio presentation has
2935          * been selected from a program.
2936          * @see #selectAudioPresentation
2937          */
2938         public int getSelectedProgramId() {
2939             synchronized (mMetadataLock) {
2940                 return mSelectedAudioProgramId;
2941             }
2942         }
2943 
2944         /**
2945          * Returns the presentation ID of the selected audio presentation.
2946          *
2947          * @return The ID of the selected audio presentation.
2948          * Returns {@value AudioPresentation.PRESENTATION_ID_UNKNOWN} if no audio presentation
2949          * has been selected.
2950          * @see #selectAudioPresentation
2951          */
2952         public int getSelectedAudioPresentationId() {
2953             synchronized (mMetadataLock) {
2954                 return mSelectedAudioPresentationId;
2955             }
2956         }
2957 
2958         /**
2959          * Responds to onAudioPresentationsChanged() and updates the internal audio presentation
2960          * information.
2961          * @return true if there is an update.
2962          */
2963         boolean updateAudioPresentations(List<AudioPresentation> audioPresentations) {
2964             synchronized (mMetadataLock) {
2965                 mAudioPresentations.clear();
2966                 for (AudioPresentation presentation : audioPresentations) {
2967                     mAudioPresentations.add(presentation);
2968                 }
2969                 return !mAudioPresentations.isEmpty();
2970             }
2971         }
2972 
2973         /**
2974          * Responds to onAudioPresentationSelected() and updates the internal audio presentation
2975          * selection information.
2976          * @return true if there is an update.
2977          */
2978         boolean updateAudioPresentationSelection(int presentationId, int programId) {
2979             synchronized (mMetadataLock) {
2980                 if ((programId != mSelectedAudioProgramId)
2981                         || (presentationId != mSelectedAudioPresentationId)) {
2982                     mSelectedAudioPresentationId = presentationId;
2983                     mSelectedAudioProgramId = programId;
2984                     return true;
2985                 }
2986             }
2987             return false;
2988         }
2989 
2990         /**
2991          * Selects a track.
2992          *
2993          * @param type The type of the track to select. The type can be
2994          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
2995          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
2996          * @param trackId The ID of the track to select. When {@code null}, the currently selected
2997          *            track of the given type will be unselected.
2998          * @see #getTracks
2999          */
3000         public void selectTrack(int type, @Nullable String trackId) {
3001             synchronized (mMetadataLock) {
3002                 if (type == TvTrackInfo.TYPE_AUDIO) {
3003                     if (trackId != null && !containsTrack(mAudioTracks, trackId)) {
3004                         Log.w(TAG, "Invalid audio trackId: " + trackId);
3005                         return;
3006                     }
3007                 } else if (type == TvTrackInfo.TYPE_VIDEO) {
3008                     if (trackId != null && !containsTrack(mVideoTracks, trackId)) {
3009                         Log.w(TAG, "Invalid video trackId: " + trackId);
3010                         return;
3011                     }
3012                 } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
3013                     if (trackId != null && !containsTrack(mSubtitleTracks, trackId)) {
3014                         Log.w(TAG, "Invalid subtitle trackId: " + trackId);
3015                         return;
3016                     }
3017                 } else {
3018                     throw new IllegalArgumentException("invalid type: " + type);
3019                 }
3020             }
3021             if (mToken == null) {
3022                 Log.w(TAG, "The session has been already released");
3023                 return;
3024             }
3025             try {
3026                 mService.selectTrack(mToken, type, trackId, mUserId);
3027             } catch (RemoteException e) {
3028                 throw e.rethrowFromSystemServer();
3029             }
3030         }
3031 
3032         private boolean containsTrack(List<TvTrackInfo> tracks, String trackId) {
3033             for (TvTrackInfo track : tracks) {
3034                 if (track.getId().equals(trackId)) {
3035                     return true;
3036                 }
3037             }
3038             return false;
3039         }
3040 
3041         /**
3042          * Returns the list of tracks for a given type. Returns {@code null} if the information is
3043          * not available.
3044          *
3045          * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO},
3046          *            {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}.
3047          * @return the list of tracks for the given type.
3048          */
3049         @Nullable
3050         public List<TvTrackInfo> getTracks(int type) {
3051             synchronized (mMetadataLock) {
3052                 if (type == TvTrackInfo.TYPE_AUDIO) {
3053                     if (mAudioTracks == null) {
3054                         return null;
3055                     }
3056                     return new ArrayList<>(mAudioTracks);
3057                 } else if (type == TvTrackInfo.TYPE_VIDEO) {
3058                     if (mVideoTracks == null) {
3059                         return null;
3060                     }
3061                     return new ArrayList<>(mVideoTracks);
3062                 } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
3063                     if (mSubtitleTracks == null) {
3064                         return null;
3065                     }
3066                     return new ArrayList<>(mSubtitleTracks);
3067                 }
3068             }
3069             throw new IllegalArgumentException("invalid type: " + type);
3070         }
3071 
3072         /**
3073          * Returns the selected track for a given type. Returns {@code null} if the information is
3074          * not available or any of the tracks for the given type is not selected.
3075          *
3076          * @return The ID of the selected track.
3077          * @see #selectTrack
3078          */
3079         @Nullable
3080         public String getSelectedTrack(int type) {
3081             synchronized (mMetadataLock) {
3082                 if (type == TvTrackInfo.TYPE_AUDIO) {
3083                     return mSelectedAudioTrackId;
3084                 } else if (type == TvTrackInfo.TYPE_VIDEO) {
3085                     return mSelectedVideoTrackId;
3086                 } else if (type == TvTrackInfo.TYPE_SUBTITLE) {
3087                     return mSelectedSubtitleTrackId;
3088                 }
3089             }
3090             throw new IllegalArgumentException("invalid type: " + type);
3091         }
3092 
3093         /**
3094          * Enables interactive app notification.
3095          *
3096          * @param enabled {@code true} if you want to enable interactive app notifications.
3097          *                {@code false} otherwise.
3098          */
3099         public void setInteractiveAppNotificationEnabled(boolean enabled) {
3100             if (mToken == null) {
3101                 Log.w(TAG, "The session has been already released");
3102                 return;
3103             }
3104             try {
3105                 mService.setInteractiveAppNotificationEnabled(mToken, enabled, mUserId);
3106                 mIAppNotificationEnabled = enabled;
3107             } catch (RemoteException e) {
3108                 throw e.rethrowFromSystemServer();
3109             }
3110         }
3111 
3112         /**
3113          * Responds to onTracksChanged() and updates the internal track information. Returns true if
3114          * there is an update.
3115          */
3116         boolean updateTracks(List<TvTrackInfo> tracks) {
3117             synchronized (mMetadataLock) {
3118                 mAudioTracks.clear();
3119                 mVideoTracks.clear();
3120                 mSubtitleTracks.clear();
3121                 for (TvTrackInfo track : tracks) {
3122                     if (track.getType() == TvTrackInfo.TYPE_AUDIO) {
3123                         mAudioTracks.add(track);
3124                     } else if (track.getType() == TvTrackInfo.TYPE_VIDEO) {
3125                         mVideoTracks.add(track);
3126                     } else if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) {
3127                         mSubtitleTracks.add(track);
3128                     }
3129                 }
3130                 return !mAudioTracks.isEmpty() || !mVideoTracks.isEmpty()
3131                         || !mSubtitleTracks.isEmpty();
3132             }
3133         }
3134 
3135         /**
3136          * Responds to onTrackSelected() and updates the internal track selection information.
3137          * Returns true if there is an update.
3138          */
3139         boolean updateTrackSelection(int type, String trackId) {
3140             synchronized (mMetadataLock) {
3141                 if (type == TvTrackInfo.TYPE_AUDIO
3142                         && !TextUtils.equals(trackId, mSelectedAudioTrackId)) {
3143                     mSelectedAudioTrackId = trackId;
3144                     return true;
3145                 } else if (type == TvTrackInfo.TYPE_VIDEO
3146                         && !TextUtils.equals(trackId, mSelectedVideoTrackId)) {
3147                     mSelectedVideoTrackId = trackId;
3148                     return true;
3149                 } else if (type == TvTrackInfo.TYPE_SUBTITLE
3150                         && !TextUtils.equals(trackId, mSelectedSubtitleTrackId)) {
3151                     mSelectedSubtitleTrackId = trackId;
3152                     return true;
3153                 }
3154             }
3155             return false;
3156         }
3157 
3158         /**
3159          * Returns the new/updated video track that contains new video size information. Returns
3160          * null if there is no video track to notify. Subsequent calls of this method results in a
3161          * non-null video track returned only by the first call and null returned by following
3162          * calls. The caller should immediately notify of the video size change upon receiving the
3163          * track.
3164          */
3165         TvTrackInfo getVideoTrackToNotify() {
3166             synchronized (mMetadataLock) {
3167                 if (!mVideoTracks.isEmpty() && mSelectedVideoTrackId != null) {
3168                     for (TvTrackInfo track : mVideoTracks) {
3169                         if (track.getId().equals(mSelectedVideoTrackId)) {
3170                             int videoWidth = track.getVideoWidth();
3171                             int videoHeight = track.getVideoHeight();
3172                             if (mVideoWidth != videoWidth || mVideoHeight != videoHeight) {
3173                                 mVideoWidth = videoWidth;
3174                                 mVideoHeight = videoHeight;
3175                                 return track;
3176                             }
3177                         }
3178                     }
3179                 }
3180             }
3181             return null;
3182         }
3183 
3184         /**
3185          * Plays a given recorded TV program.
3186          */
3187         void timeShiftPlay(Uri recordedProgramUri) {
3188             if (mToken == null) {
3189                 Log.w(TAG, "The session has been already released");
3190                 return;
3191             }
3192             try {
3193                 mService.timeShiftPlay(mToken, recordedProgramUri, mUserId);
3194             } catch (RemoteException e) {
3195                 throw e.rethrowFromSystemServer();
3196             }
3197         }
3198 
3199         /**
3200          * Pauses the playback. Call {@link #timeShiftResume()} to restart the playback.
3201          */
3202         void timeShiftPause() {
3203             if (mToken == null) {
3204                 Log.w(TAG, "The session has been already released");
3205                 return;
3206             }
3207             try {
3208                 mService.timeShiftPause(mToken, mUserId);
3209             } catch (RemoteException e) {
3210                 throw e.rethrowFromSystemServer();
3211             }
3212         }
3213 
3214         /**
3215          * Resumes the playback. No-op if it is already playing the channel.
3216          */
3217         void timeShiftResume() {
3218             if (mToken == null) {
3219                 Log.w(TAG, "The session has been already released");
3220                 return;
3221             }
3222             try {
3223                 mService.timeShiftResume(mToken, mUserId);
3224             } catch (RemoteException e) {
3225                 throw e.rethrowFromSystemServer();
3226             }
3227         }
3228 
3229         /**
3230          * Seeks to a specified time position.
3231          *
3232          * <p>Normally, the position is given within range between the start and the current time,
3233          * inclusively.
3234          *
3235          * @param timeMs The time position to seek to, in milliseconds since the epoch.
3236          * @see TvView.TimeShiftPositionCallback#onTimeShiftStartPositionChanged
3237          */
3238         void timeShiftSeekTo(long timeMs) {
3239             if (mToken == null) {
3240                 Log.w(TAG, "The session has been already released");
3241                 return;
3242             }
3243             try {
3244                 mService.timeShiftSeekTo(mToken, timeMs, mUserId);
3245             } catch (RemoteException e) {
3246                 throw e.rethrowFromSystemServer();
3247             }
3248         }
3249 
3250         /**
3251          * Sets playback rate using {@link android.media.PlaybackParams}.
3252          *
3253          * @param params The playback params.
3254          */
3255         void timeShiftSetPlaybackParams(PlaybackParams params) {
3256             if (mToken == null) {
3257                 Log.w(TAG, "The session has been already released");
3258                 return;
3259             }
3260             try {
3261                 mService.timeShiftSetPlaybackParams(mToken, params, mUserId);
3262             } catch (RemoteException e) {
3263                 throw e.rethrowFromSystemServer();
3264             }
3265         }
3266 
3267         /**
3268          * Sets time shift mode.
3269          *
3270          * @param mode The time shift mode. The value is one of the following:
3271          * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
3272          * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
3273          * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
3274          * @hide
3275          */
3276         void timeShiftSetMode(@TimeShiftMode int mode) {
3277             if (mToken == null) {
3278                 Log.w(TAG, "The session has been already released");
3279                 return;
3280             }
3281             try {
3282                 mService.timeShiftSetMode(mToken, mode, mUserId);
3283             } catch (RemoteException e) {
3284                 throw e.rethrowFromSystemServer();
3285             }
3286         }
3287 
3288         /**
3289          * Enable/disable position tracking.
3290          *
3291          * @param enable {@code true} to enable tracking, {@code false} otherwise.
3292          */
3293         void timeShiftEnablePositionTracking(boolean enable) {
3294             if (mToken == null) {
3295                 Log.w(TAG, "The session has been already released");
3296                 return;
3297             }
3298             try {
3299                 mService.timeShiftEnablePositionTracking(mToken, enable, mUserId);
3300             } catch (RemoteException e) {
3301                 throw e.rethrowFromSystemServer();
3302             }
3303         }
3304 
3305         /**
3306          * Sends TV messages to the service for testing purposes
3307          */
3308         public void notifyTvMessage(int type, Bundle data) {
3309             try {
3310                 mService.notifyTvMessage(mToken, type, data, mUserId);
3311             } catch (RemoteException e) {
3312                 throw e.rethrowFromSystemServer();
3313             }
3314         }
3315 
3316         /**
3317          * Sets whether the TV message of the specific type should be enabled.
3318          */
3319         public void setTvMessageEnabled(int type, boolean enabled) {
3320             try {
3321                 mService.setTvMessageEnabled(mToken, type, enabled, mUserId);
3322             } catch (RemoteException e) {
3323                 throw e.rethrowFromSystemServer();
3324             }
3325         }
3326 
3327         /**
3328          * Starts TV program recording in the current recording session.
3329          *
3330          * @param programUri The URI for the TV program to record as a hint, built by
3331          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
3332          */
3333         void startRecording(@Nullable Uri programUri) {
3334             startRecording(programUri, null);
3335         }
3336 
3337         /**
3338          * Starts TV program recording in the current recording session.
3339          *
3340          * @param programUri The URI for the TV program to record as a hint, built by
3341          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
3342          * @param params A set of extra parameters which might be handled with this event.
3343          */
3344         void startRecording(@Nullable Uri programUri, @Nullable Bundle params) {
3345             if (mToken == null) {
3346                 Log.w(TAG, "The session has been already released");
3347                 return;
3348             }
3349             try {
3350                 mService.startRecording(mToken, programUri, params, mUserId);
3351             } catch (RemoteException e) {
3352                 throw e.rethrowFromSystemServer();
3353             }
3354         }
3355 
3356         /**
3357          * Stops TV program recording in the current recording session.
3358          */
3359         void stopRecording() {
3360             if (mToken == null) {
3361                 Log.w(TAG, "The session has been already released");
3362                 return;
3363             }
3364             try {
3365                 mService.stopRecording(mToken, mUserId);
3366             } catch (RemoteException e) {
3367                 throw e.rethrowFromSystemServer();
3368             }
3369         }
3370 
3371         /**
3372          * Pauses TV program recording in the current recording session.
3373          *
3374          * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped
3375          *            name, i.e. prefixed with a package name you own, so that different developers
3376          *            will not create conflicting keys.
3377          *        {@link TvRecordingClient#pauseRecording(Bundle)}.
3378          */
3379         void pauseRecording(@NonNull Bundle params) {
3380             if (mToken == null) {
3381                 Log.w(TAG, "The session has been already released");
3382                 return;
3383             }
3384             try {
3385                 mService.pauseRecording(mToken, params, mUserId);
3386             } catch (RemoteException e) {
3387                 throw e.rethrowFromSystemServer();
3388             }
3389         }
3390 
3391         /**
3392          * Resumes TV program recording in the current recording session.
3393          *
3394          * @param params Domain-specific data for this request. Keys <em>must</em> be a scoped
3395          *            name, i.e. prefixed with a package name you own, so that different developers
3396          *            will not create conflicting keys.
3397          *        {@link TvRecordingClient#resumeRecording(Bundle)}.
3398          */
3399         void resumeRecording(@NonNull Bundle params) {
3400             if (mToken == null) {
3401                 Log.w(TAG, "The session has been already released");
3402                 return;
3403             }
3404             try {
3405                 mService.resumeRecording(mToken, params, mUserId);
3406             } catch (RemoteException e) {
3407                 throw e.rethrowFromSystemServer();
3408             }
3409         }
3410 
3411         /**
3412          * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle)
3413          * TvInputService.Session.appPrivateCommand()} on the current TvView.
3414          *
3415          * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
3416          *            i.e. prefixed with a package name you own, so that different developers will
3417          *            not create conflicting commands.
3418          * @param data Any data to include with the command.
3419          */
3420         public void sendAppPrivateCommand(String action, Bundle data) {
3421             if (mToken == null) {
3422                 Log.w(TAG, "The session has been already released");
3423                 return;
3424             }
3425             try {
3426                 mService.sendAppPrivateCommand(mToken, action, data, mUserId);
3427             } catch (RemoteException e) {
3428                 throw e.rethrowFromSystemServer();
3429             }
3430         }
3431 
3432         /**
3433          * Creates an overlay view. Once the overlay view is created, {@link #relayoutOverlayView}
3434          * should be called whenever the layout of its containing view is changed.
3435          * {@link #removeOverlayView()} should be called to remove the overlay view.
3436          * Since a session can have only one overlay view, this method should be called only once
3437          * or it can be called again after calling {@link #removeOverlayView()}.
3438          *
3439          * @param view A view playing TV.
3440          * @param frame A position of the overlay view.
3441          * @throws IllegalStateException if {@code view} is not attached to a window.
3442          */
3443         void createOverlayView(@NonNull View view, @NonNull Rect frame) {
3444             Preconditions.checkNotNull(view);
3445             Preconditions.checkNotNull(frame);
3446             if (view.getWindowToken() == null) {
3447                 throw new IllegalStateException("view must be attached to a window");
3448             }
3449             if (mToken == null) {
3450                 Log.w(TAG, "The session has been already released");
3451                 return;
3452             }
3453             try {
3454                 mService.createOverlayView(mToken, view.getWindowToken(), frame, mUserId);
3455             } catch (RemoteException e) {
3456                 throw e.rethrowFromSystemServer();
3457             }
3458         }
3459 
3460         /**
3461          * Relayouts the current overlay view.
3462          *
3463          * @param frame A new position of the overlay view.
3464          */
3465         void relayoutOverlayView(@NonNull Rect frame) {
3466             Preconditions.checkNotNull(frame);
3467             if (mToken == null) {
3468                 Log.w(TAG, "The session has been already released");
3469                 return;
3470             }
3471             try {
3472                 mService.relayoutOverlayView(mToken, frame, mUserId);
3473             } catch (RemoteException e) {
3474                 throw e.rethrowFromSystemServer();
3475             }
3476         }
3477 
3478         /**
3479          * Removes the current overlay view.
3480          */
3481         void removeOverlayView() {
3482             if (mToken == null) {
3483                 Log.w(TAG, "The session has been already released");
3484                 return;
3485             }
3486             try {
3487                 mService.removeOverlayView(mToken, mUserId);
3488             } catch (RemoteException e) {
3489                 throw e.rethrowFromSystemServer();
3490             }
3491         }
3492 
3493         /**
3494          * Requests to unblock content blocked by parental controls.
3495          */
3496         void unblockContent(@NonNull TvContentRating unblockedRating) {
3497             Preconditions.checkNotNull(unblockedRating);
3498             if (mToken == null) {
3499                 Log.w(TAG, "The session has been already released");
3500                 return;
3501             }
3502             try {
3503                 mService.unblockContent(mToken, unblockedRating.flattenToString(), mUserId);
3504             } catch (RemoteException e) {
3505                 throw e.rethrowFromSystemServer();
3506             }
3507         }
3508 
3509         /**
3510          * Dispatches an input event to this session.
3511          *
3512          * @param event An {@link InputEvent} to dispatch. Cannot be {@code null}.
3513          * @param token A token used to identify the input event later in the callback.
3514          * @param callback A callback used to receive the dispatch result. Cannot be {@code null}.
3515          * @param handler A {@link Handler} that the dispatch result will be delivered to. Cannot be
3516          *            {@code null}.
3517          * @return Returns {@link #DISPATCH_HANDLED} if the event was handled. Returns
3518          *         {@link #DISPATCH_NOT_HANDLED} if the event was not handled. Returns
3519          *         {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the callback will
3520          *         be invoked later.
3521          * @hide
3522          */
3523         public int dispatchInputEvent(@NonNull InputEvent event, Object token,
3524                 @NonNull FinishedInputEventCallback callback, @NonNull Handler handler) {
3525             Preconditions.checkNotNull(event);
3526             Preconditions.checkNotNull(callback);
3527             Preconditions.checkNotNull(handler);
3528             synchronized (mHandler) {
3529                 if (mChannel == null) {
3530                     return DISPATCH_NOT_HANDLED;
3531                 }
3532                 PendingEvent p = obtainPendingEventLocked(event, token, callback, handler);
3533                 if (Looper.myLooper() == Looper.getMainLooper()) {
3534                     // Already running on the main thread so we can send the event immediately.
3535                     return sendInputEventOnMainLooperLocked(p);
3536                 }
3537 
3538                 // Post the event to the main thread.
3539                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_SEND_INPUT_EVENT, p);
3540                 msg.setAsynchronous(true);
3541                 mHandler.sendMessage(msg);
3542                 return DISPATCH_IN_PROGRESS;
3543             }
3544         }
3545 
3546         /**
3547          * Callback that is invoked when an input event that was dispatched to this session has been
3548          * finished.
3549          *
3550          * @hide
3551          */
3552         public interface FinishedInputEventCallback {
3553             /**
3554              * Called when the dispatched input event is finished.
3555              *
3556              * @param token A token passed to {@link #dispatchInputEvent}.
3557              * @param handled {@code true} if the dispatched input event was handled properly.
3558              *            {@code false} otherwise.
3559              */
3560             void onFinishedInputEvent(Object token, boolean handled);
3561         }
3562 
3563         // Must be called on the main looper
3564         private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
3565             synchronized (mHandler) {
3566                 int result = sendInputEventOnMainLooperLocked(p);
3567                 if (result == DISPATCH_IN_PROGRESS) {
3568                     return;
3569                 }
3570             }
3571 
3572             invokeFinishedInputEventCallback(p, false);
3573         }
3574 
3575         private int sendInputEventOnMainLooperLocked(PendingEvent p) {
3576             if (mChannel != null) {
3577                 if (mSender == null) {
3578                     mSender = new TvInputEventSender(mChannel, mHandler.getLooper());
3579                 }
3580 
3581                 final InputEvent event = p.mEvent;
3582                 final int seq = event.getSequenceNumber();
3583                 if (mSender.sendInputEvent(seq, event)) {
3584                     mPendingEvents.put(seq, p);
3585                     Message msg = mHandler.obtainMessage(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
3586                     msg.setAsynchronous(true);
3587                     mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
3588                     return DISPATCH_IN_PROGRESS;
3589                 }
3590 
3591                 Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
3592                         + event);
3593             }
3594             return DISPATCH_NOT_HANDLED;
3595         }
3596 
3597         void finishedInputEvent(int seq, boolean handled, boolean timeout) {
3598             final PendingEvent p;
3599             synchronized (mHandler) {
3600                 int index = mPendingEvents.indexOfKey(seq);
3601                 if (index < 0) {
3602                     return; // spurious, event already finished or timed out
3603                 }
3604 
3605                 p = mPendingEvents.valueAt(index);
3606                 mPendingEvents.removeAt(index);
3607 
3608                 if (timeout) {
3609                     Log.w(TAG, "Timeout waiting for session to handle input event after "
3610                             + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
3611                 } else {
3612                     mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
3613                 }
3614             }
3615 
3616             invokeFinishedInputEventCallback(p, handled);
3617         }
3618 
3619         // Assumes the event has already been removed from the queue.
3620         void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
3621             p.mHandled = handled;
3622             if (p.mEventHandler.getLooper().isCurrentThread()) {
3623                 // Already running on the callback handler thread so we can send the callback
3624                 // immediately.
3625                 p.run();
3626             } else {
3627                 // Post the event to the callback handler thread.
3628                 // In this case, the callback will be responsible for recycling the event.
3629                 Message msg = Message.obtain(p.mEventHandler, p);
3630                 msg.setAsynchronous(true);
3631                 msg.sendToTarget();
3632             }
3633         }
3634 
3635         private void flushPendingEventsLocked() {
3636             mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
3637 
3638             final int count = mPendingEvents.size();
3639             for (int i = 0; i < count; i++) {
3640                 int seq = mPendingEvents.keyAt(i);
3641                 Message msg = mHandler.obtainMessage(InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
3642                 msg.setAsynchronous(true);
3643                 msg.sendToTarget();
3644             }
3645         }
3646 
3647         private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
3648                 FinishedInputEventCallback callback, Handler handler) {
3649             PendingEvent p = mPendingEventPool.acquire();
3650             if (p == null) {
3651                 p = new PendingEvent();
3652             }
3653             p.mEvent = event;
3654             p.mEventToken = token;
3655             p.mCallback = callback;
3656             p.mEventHandler = handler;
3657             return p;
3658         }
3659 
3660         private void recyclePendingEventLocked(PendingEvent p) {
3661             p.recycle();
3662             mPendingEventPool.release(p);
3663         }
3664 
3665         IBinder getToken() {
3666             return mToken;
3667         }
3668 
3669         private void releaseInternal() {
3670             mToken = null;
3671             synchronized (mHandler) {
3672                 if (mChannel != null) {
3673                     if (mSender != null) {
3674                         flushPendingEventsLocked();
3675                         mSender.dispose();
3676                         mSender = null;
3677                     }
3678                     mChannel.dispose();
3679                     mChannel = null;
3680                 }
3681             }
3682             synchronized (mSessionCallbackRecordMap) {
3683                 mSessionCallbackRecordMap.delete(mSeq);
3684             }
3685         }
3686 
3687         public void requestBroadcastInfo(BroadcastInfoRequest request) {
3688             if (mToken == null) {
3689                 Log.w(TAG, "The session has been already released");
3690                 return;
3691             }
3692             try {
3693                 mService.requestBroadcastInfo(mToken, request, mUserId);
3694             } catch (RemoteException e) {
3695                 throw e.rethrowFromSystemServer();
3696             }
3697         }
3698 
3699         /**
3700          * Removes broadcast info.
3701          * @param requestId the corresponding request ID sent from
3702          *                  {@link #requestBroadcastInfo(android.media.tv.BroadcastInfoRequest)}
3703          */
3704         public void removeBroadcastInfo(int requestId) {
3705             if (mToken == null) {
3706                 Log.w(TAG, "The session has been already released");
3707                 return;
3708             }
3709             try {
3710                 mService.removeBroadcastInfo(mToken, requestId, mUserId);
3711             } catch (RemoteException e) {
3712                 throw e.rethrowFromSystemServer();
3713             }
3714         }
3715 
3716         public void requestAd(AdRequest request) {
3717             if (mToken == null) {
3718                 Log.w(TAG, "The session has been already released");
3719                 return;
3720             }
3721             try {
3722                 mService.requestAd(mToken, request, mUserId);
3723             } catch (RemoteException e) {
3724                 throw e.rethrowFromSystemServer();
3725             }
3726         }
3727 
3728         /**
3729          * Notifies when the advertisement buffer is filled and ready to be read.
3730          */
3731         public void notifyAdBufferReady(AdBuffer buffer) {
3732             if (mToken == null) {
3733                 Log.w(TAG, "The session has been already released");
3734                 return;
3735             }
3736             try {
3737                 mService.notifyAdBufferReady(mToken, buffer, mUserId);
3738             } catch (RemoteException e) {
3739                 throw e.rethrowFromSystemServer();
3740             } finally {
3741                 if (buffer != null) {
3742                     buffer.getSharedMemory().close();
3743                 }
3744             }
3745         }
3746 
3747         private final class InputEventHandler extends Handler {
3748             public static final int MSG_SEND_INPUT_EVENT = 1;
3749             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
3750             public static final int MSG_FLUSH_INPUT_EVENT = 3;
3751 
3752             InputEventHandler(Looper looper) {
3753                 super(looper, null, true);
3754             }
3755 
3756             @Override
3757             public void handleMessage(Message msg) {
3758                 switch (msg.what) {
3759                     case MSG_SEND_INPUT_EVENT: {
3760                         sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
3761                         return;
3762                     }
3763                     case MSG_TIMEOUT_INPUT_EVENT: {
3764                         finishedInputEvent(msg.arg1, false, true);
3765                         return;
3766                     }
3767                     case MSG_FLUSH_INPUT_EVENT: {
3768                         finishedInputEvent(msg.arg1, false, false);
3769                         return;
3770                     }
3771                 }
3772             }
3773         }
3774 
3775         private final class TvInputEventSender extends InputEventSender {
3776             public TvInputEventSender(InputChannel inputChannel, Looper looper) {
3777                 super(inputChannel, looper);
3778             }
3779 
3780             @Override
3781             public void onInputEventFinished(int seq, boolean handled) {
3782                 finishedInputEvent(seq, handled, false);
3783             }
3784         }
3785 
3786         private final class PendingEvent implements Runnable {
3787             public InputEvent mEvent;
3788             public Object mEventToken;
3789             public FinishedInputEventCallback mCallback;
3790             public Handler mEventHandler;
3791             public boolean mHandled;
3792 
3793             public void recycle() {
3794                 mEvent = null;
3795                 mEventToken = null;
3796                 mCallback = null;
3797                 mEventHandler = null;
3798                 mHandled = false;
3799             }
3800 
3801             @Override
3802             public void run() {
3803                 mCallback.onFinishedInputEvent(mEventToken, mHandled);
3804 
3805                 synchronized (mEventHandler) {
3806                     recyclePendingEventLocked(this);
3807                 }
3808             }
3809         }
3810     }
3811 
3812     /**
3813      * The Hardware provides the per-hardware functionality of TV hardware.
3814      *
3815      * <p>TV hardware is physical hardware attached to the Android device; for example, HDMI ports,
3816      * Component/Composite ports, etc. Specifically, logical devices such as HDMI CEC logical
3817      * devices don't fall into this category.
3818      *
3819      * @hide
3820      */
3821     @SystemApi
3822     public final static class Hardware {
3823         private final ITvInputHardware mInterface;
3824 
3825         private Hardware(ITvInputHardware hardwareInterface) {
3826             mInterface = hardwareInterface;
3827         }
3828 
3829         private ITvInputHardware getInterface() {
3830             return mInterface;
3831         }
3832 
3833         public boolean setSurface(Surface surface, TvStreamConfig config) {
3834             try {
3835                 return mInterface.setSurface(surface, config);
3836             } catch (RemoteException e) {
3837                 throw new RuntimeException(e);
3838             }
3839         }
3840 
3841         public void setStreamVolume(float volume) {
3842             try {
3843                 mInterface.setStreamVolume(volume);
3844             } catch (RemoteException e) {
3845                 throw new RuntimeException(e);
3846             }
3847         }
3848 
3849         /** @removed */
3850         @SystemApi
3851         public boolean dispatchKeyEventToHdmi(KeyEvent event) {
3852             return false;
3853         }
3854 
3855         /**
3856          * Override default audio sink from audio policy.
3857          *
3858          * @param audioType device type of the audio sink to override with.
3859          * @param audioAddress device address of the audio sink to override with.
3860          * @param samplingRate desired sampling rate. Use default when it's 0.
3861          * @param channelMask desired channel mask. Use default when it's
3862          *        AudioFormat.CHANNEL_OUT_DEFAULT.
3863          * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
3864          */
3865         public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
3866                 int channelMask, int format) {
3867             try {
3868                 mInterface.overrideAudioSink(audioType, audioAddress, samplingRate, channelMask,
3869                         format);
3870             } catch (RemoteException e) {
3871                 throw new RuntimeException(e);
3872             }
3873         }
3874 
3875         /**
3876          * Override default audio sink from audio policy.
3877          *
3878          * @param device {@link android.media.AudioDeviceInfo} to use.
3879          * @param samplingRate desired sampling rate. Use default when it's 0.
3880          * @param channelMask desired channel mask. Use default when it's
3881          *        AudioFormat.CHANNEL_OUT_DEFAULT.
3882          * @param format desired format. Use default when it's AudioFormat.ENCODING_DEFAULT.
3883          */
3884         public void overrideAudioSink(@NonNull AudioDeviceInfo device,
3885                 @IntRange(from = 0) int samplingRate,
3886                 int channelMask, @Encoding int format) {
3887             Objects.requireNonNull(device);
3888             try {
3889                 mInterface.overrideAudioSink(
3890                         AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType()),
3891                         device.getAddress(), samplingRate, channelMask, format);
3892             } catch (RemoteException e) {
3893                 throw new RuntimeException(e);
3894             }
3895         }
3896     }
3897 }
3898