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