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