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.FloatRange;
20 import android.annotation.IntDef;
21 import android.annotation.MainThread;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.SuppressLint;
25 import android.annotation.SystemApi;
26 import android.app.ActivityManager;
27 import android.app.Service;
28 import android.compat.annotation.UnsupportedAppUsage;
29 import android.content.AttributionSource;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.graphics.PixelFormat;
33 import android.graphics.Rect;
34 import android.hardware.hdmi.HdmiDeviceInfo;
35 import android.media.AudioPresentation;
36 import android.media.PlaybackParams;
37 import android.net.Uri;
38 import android.os.AsyncTask;
39 import android.os.Build;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.IBinder;
43 import android.os.Message;
44 import android.os.Process;
45 import android.os.RemoteCallbackList;
46 import android.os.RemoteException;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.view.Gravity;
50 import android.view.InputChannel;
51 import android.view.InputDevice;
52 import android.view.InputEvent;
53 import android.view.InputEventReceiver;
54 import android.view.KeyEvent;
55 import android.view.MotionEvent;
56 import android.view.Surface;
57 import android.view.View;
58 import android.view.ViewRootImpl;
59 import android.view.WindowManager;
60 import android.view.accessibility.CaptioningManager;
61 import android.widget.FrameLayout;
62 
63 import com.android.internal.os.SomeArgs;
64 import com.android.internal.util.Preconditions;
65 
66 import java.io.IOException;
67 import java.lang.annotation.Retention;
68 import java.lang.annotation.RetentionPolicy;
69 import java.util.ArrayList;
70 import java.util.Arrays;
71 import java.util.List;
72 
73 /**
74  * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
75  * provides pass-through video or broadcast TV programs.
76  *
77  * <p>Applications will not normally use this service themselves, instead relying on the standard
78  * interaction provided by {@link TvView}. Those implementing TV input services should normally do
79  * so by deriving from this class and providing their own session implementation based on
80  * {@link TvInputService.Session}. All TV input services must require that clients hold the
81  * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this
82  * permission is not specified in the manifest, the system will refuse to bind to that TV input
83  * service.
84  */
85 public abstract class TvInputService extends Service {
86     private static final boolean DEBUG = false;
87     private static final String TAG = "TvInputService";
88 
89     private static final int DETACH_OVERLAY_VIEW_TIMEOUT_MS = 5000;
90 
91     /**
92      * This is the interface name that a service implementing a TV input should say that it support
93      * -- that is, this is the action it uses for its intent filter. To be supported, the service
94      * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
95      * other applications cannot abuse it.
96      */
97     public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
98 
99     /**
100      * Name under which a TvInputService component publishes information about itself.
101      * This meta-data must reference an XML resource containing an
102      * <code>&lt;{@link android.R.styleable#TvInputService tv-input}&gt;</code>
103      * tag.
104      */
105     public static final String SERVICE_META_DATA = "android.media.tv.input";
106 
107     /**
108      * Prioirity hint from use case types.
109      *
110      * @hide
111      */
112     @IntDef(prefix = "PRIORITY_HINT_USE_CASE_TYPE_",
113             value = {PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND, PRIORITY_HINT_USE_CASE_TYPE_SCAN,
114                     PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, PRIORITY_HINT_USE_CASE_TYPE_LIVE,
115                     PRIORITY_HINT_USE_CASE_TYPE_RECORD})
116     @Retention(RetentionPolicy.SOURCE)
117     public @interface PriorityHintUseCaseType {}
118 
119     /**
120      * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
121      * int)}: Background. TODO Link: Tuner#Tuner(Context, string, int).
122      */
123     public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100;
124 
125     /**
126      * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
127      * int)}: Scan. TODO Link: Tuner#Tuner(Context, string, int).
128      */
129     public static final int PRIORITY_HINT_USE_CASE_TYPE_SCAN = 200;
130 
131     /**
132      * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
133      * int)}: Playback. TODO Link: Tuner#Tuner(Context, string, int).
134      */
135     public static final int PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK = 300;
136 
137     /**
138      * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
139      * int)}: Live. TODO Link: Tuner#Tuner(Context, string, int).
140      */
141     public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400;
142 
143     /**
144      * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String,
145      * int)}: Record. TODO Link: Tuner#Tuner(Context, string, int).
146      */
147     public static final int PRIORITY_HINT_USE_CASE_TYPE_RECORD = 500;
148 
149     /**
150      * Handler instance to handle request from TV Input Manager Service. Should be run in the main
151      * looper to be synchronously run with {@code Session.mHandler}.
152      */
153     private final Handler mServiceHandler = new ServiceHandler();
154     private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
155             new RemoteCallbackList<>();
156 
157     private TvInputManager mTvInputManager;
158 
159     @Override
onBind(Intent intent)160     public final IBinder onBind(Intent intent) {
161         ITvInputService.Stub tvInputServiceBinder = new ITvInputService.Stub() {
162             @Override
163             public void registerCallback(ITvInputServiceCallback cb) {
164                 if (cb != null) {
165                     mCallbacks.register(cb);
166                 }
167             }
168 
169             @Override
170             public void unregisterCallback(ITvInputServiceCallback cb) {
171                 if (cb != null) {
172                     mCallbacks.unregister(cb);
173                 }
174             }
175 
176             @Override
177             public void createSession(InputChannel channel, ITvInputSessionCallback cb,
178                     String inputId, String sessionId, AttributionSource tvAppAttributionSource) {
179                 if (channel == null) {
180                     Log.w(TAG, "Creating session without input channel");
181                 }
182                 if (cb == null) {
183                     return;
184                 }
185                 SomeArgs args = SomeArgs.obtain();
186                 args.arg1 = channel;
187                 args.arg2 = cb;
188                 args.arg3 = inputId;
189                 args.arg4 = sessionId;
190                 args.arg5 = tvAppAttributionSource;
191                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION,
192                         args).sendToTarget();
193             }
194 
195             @Override
196             public void createRecordingSession(ITvInputSessionCallback cb, String inputId,
197                     String sessionId) {
198                 if (cb == null) {
199                     return;
200                 }
201                 SomeArgs args = SomeArgs.obtain();
202                 args.arg1 = cb;
203                 args.arg2 = inputId;
204                 args.arg3 = sessionId;
205                 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args)
206                         .sendToTarget();
207             }
208 
209             @Override
210             public List<String>  getAvailableExtensionInterfaceNames() {
211                 return TvInputService.this.getAvailableExtensionInterfaceNames();
212             }
213 
214             @Override
215             public IBinder getExtensionInterface(String name) {
216                 return TvInputService.this.getExtensionInterface(name);
217             }
218 
219             @Override
220             public String getExtensionInterfacePermission(String name) {
221                 return TvInputService.this.getExtensionInterfacePermission(name);
222             }
223 
224             @Override
225             public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
226                 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_INPUT,
227                         hardwareInfo).sendToTarget();
228             }
229 
230             @Override
231             public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
232                 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_INPUT,
233                         hardwareInfo).sendToTarget();
234             }
235 
236             @Override
237             public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
238                 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_INPUT,
239                         deviceInfo).sendToTarget();
240             }
241 
242             @Override
243             public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
244                 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT,
245                         deviceInfo).sendToTarget();
246             }
247 
248             @Override
249             public void notifyHdmiDeviceUpdated(HdmiDeviceInfo deviceInfo) {
250                 mServiceHandler.obtainMessage(ServiceHandler.DO_UPDATE_HDMI_INPUT,
251                         deviceInfo).sendToTarget();
252             }
253         };
254         IBinder ext = createExtension();
255         if (ext != null) {
256             tvInputServiceBinder.setExtension(ext);
257         }
258         return tvInputServiceBinder;
259     }
260 
261     /**
262      * Returns a new {@link android.os.Binder}
263      *
264      * <p> if an extension is provided on top of existing {@link TvInputService}; otherwise,
265      * return {@code null}. Override to provide extended interface.
266      *
267      * @see android.os.Binder#setExtension(IBinder)
268      * @hide
269      */
270     @Nullable
271     @SystemApi
createExtension()272     public IBinder createExtension() {
273         return null;
274     }
275 
276     /**
277      * Returns available extension interfaces. This can be used to provide domain-specific
278      * features that are only known between certain hardware TV inputs and their clients.
279      *
280      * <p>Note that this service-level extension interface mechanism is only for hardware
281      * TV inputs that are bound even when sessions are not created.
282      *
283      * @return a non-null list of available extension interface names. An empty list
284      *         indicates the TV input doesn't support any extension interfaces.
285      * @see #getExtensionInterface
286      * @see #getExtensionInterfacePermission
287      * @hide
288      */
289     @NonNull
290     @SystemApi
getAvailableExtensionInterfaceNames()291     public List<String> getAvailableExtensionInterfaceNames() {
292         return new ArrayList<>();
293     }
294 
295     /**
296      * Returns an extension interface. This can be used to provide domain-specific features
297      * that are only known between certain hardware TV inputs and their clients.
298      *
299      * <p>Note that this service-level extension interface mechanism is only for hardware
300      * TV inputs that are bound even when sessions are not created.
301      *
302      * @param name The extension interface name.
303      * @return an {@link IBinder} for the given extension interface, {@code null} if the TV input
304      *         doesn't support the given extension interface.
305      * @see #getAvailableExtensionInterfaceNames
306      * @see #getExtensionInterfacePermission
307      * @hide
308      */
309     @Nullable
310     @SystemApi
getExtensionInterface(@onNull String name)311     public IBinder getExtensionInterface(@NonNull String name) {
312         return null;
313     }
314 
315     /**
316      * Returns a permission for the given extension interface. This can be used to provide
317      * domain-specific features that are only known between certain hardware TV inputs and their
318      * clients.
319      *
320      * <p>Note that this service-level extension interface mechanism is only for hardware
321      * TV inputs that are bound even when sessions are not created.
322      *
323      * @param name The extension interface name.
324      * @return a name of the permission being checked for the given extension interface,
325      *         {@code null} if there is no required permission, or if the TV input doesn't
326      *         support the given extension interface.
327      * @see #getAvailableExtensionInterfaceNames
328      * @see #getExtensionInterface
329      * @hide
330      */
331     @Nullable
332     @SystemApi
getExtensionInterfacePermission(@onNull String name)333     public String getExtensionInterfacePermission(@NonNull String name) {
334         return null;
335     }
336 
337     /**
338      * Returns a concrete implementation of {@link Session}.
339      *
340      * <p>May return {@code null} if this TV input service fails to create a session for some
341      * reason. If TV input represents an external device connected to a hardware TV input,
342      * {@link HardwareSession} should be returned.
343      *
344      * @param inputId The ID of the TV input associated with the session.
345      */
346     @Nullable
onCreateSession(@onNull String inputId)347     public abstract Session onCreateSession(@NonNull String inputId);
348 
349     /**
350      * Returns a concrete implementation of {@link RecordingSession}.
351      *
352      * <p>May return {@code null} if this TV input service fails to create a recording session for
353      * some reason.
354      *
355      * @param inputId The ID of the TV input associated with the recording session.
356      */
357     @Nullable
onCreateRecordingSession(@onNull String inputId)358     public RecordingSession onCreateRecordingSession(@NonNull String inputId) {
359         return null;
360     }
361 
362     /**
363      * Returns a concrete implementation of {@link Session}.
364      *
365      * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
366      * it needs to override this method to get the sessionId passed. When no overriding, this method
367      * calls {@link #onCreateSession(String)} defaultly.
368      *
369      * @param inputId The ID of the TV input associated with the session.
370      * @param sessionId the unique sessionId created by TIF when session is created.
371      */
372     @Nullable
onCreateSession(@onNull String inputId, @NonNull String sessionId)373     public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) {
374         return onCreateSession(inputId);
375     }
376 
377     /**
378      * Returns a concrete implementation of {@link Session}.
379      *
380      * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager and
381      * needs to specify custom AttributionSource to AudioTrack, it needs to override this method to
382      * get the sessionId and AttrubutionSource passed. When no overriding, this method calls {@link
383      * #onCreateSession(String, String)} defaultly.
384      *
385      * @param inputId The ID of the TV input associated with the session.
386      * @param sessionId the unique sessionId created by TIF when session is created.
387      * @param tvAppAttributionSource The Attribution Source of the TV App.
388      */
389     @Nullable
onCreateSession(@onNull String inputId, @NonNull String sessionId, @NonNull AttributionSource tvAppAttributionSource)390     public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId,
391             @NonNull AttributionSource tvAppAttributionSource) {
392         return onCreateSession(inputId, sessionId);
393     }
394 
395     /**
396      * Returns a concrete implementation of {@link RecordingSession}.
397      *
398      * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
399      * it needs to override this method to get the sessionId passed. When no overriding, this method
400      * calls {@link #onCreateRecordingSession(String)} defaultly.
401      *
402      * @param inputId The ID of the TV input associated with the recording session.
403      * @param sessionId the unique sessionId created by TIF when session is created.
404      */
405     @Nullable
onCreateRecordingSession( @onNull String inputId, @NonNull String sessionId)406     public RecordingSession onCreateRecordingSession(
407             @NonNull String inputId, @NonNull String sessionId) {
408         return onCreateRecordingSession(inputId);
409     }
410 
411     /**
412      * Returns a new {@link TvInputInfo} object if this service is responsible for
413      * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
414      * ignoring all hardware input.
415      *
416      * @param hardwareInfo {@link TvInputHardwareInfo} object just added.
417      * @hide
418      */
419     @Nullable
420     @SystemApi
onHardwareAdded(TvInputHardwareInfo hardwareInfo)421     public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
422         return null;
423     }
424 
425     /**
426      * Returns the input ID for {@code deviceId} if it is handled by this service;
427      * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware
428      * input.
429      *
430      * @param hardwareInfo {@link TvInputHardwareInfo} object just removed.
431      * @hide
432      */
433     @Nullable
434     @SystemApi
onHardwareRemoved(TvInputHardwareInfo hardwareInfo)435     public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
436         return null;
437     }
438 
439     /**
440      * Returns a new {@link TvInputInfo} object if this service is responsible for
441      * {@code deviceInfo}; otherwise, return {@code null}. Override to modify default behavior of
442      * ignoring all HDMI logical input device.
443      *
444      * @param deviceInfo {@link HdmiDeviceInfo} object just added.
445      * @hide
446      */
447     @Nullable
448     @SystemApi
onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo)449     public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
450         return null;
451     }
452 
453     /**
454      * Returns the input ID for {@code deviceInfo} if it is handled by this service; otherwise,
455      * return {@code null}. Override to modify default behavior of ignoring all HDMI logical input
456      * device.
457      *
458      * @param deviceInfo {@link HdmiDeviceInfo} object just removed.
459      * @hide
460      */
461     @Nullable
462     @SystemApi
onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo)463     public String onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
464         return null;
465     }
466 
467     /**
468      * Called when {@code deviceInfo} is updated.
469      *
470      * <p>The changes are usually cuased by the corresponding HDMI-CEC logical device.
471      *
472      * <p>The default behavior ignores all changes.
473      *
474      * <p>The TV input service responsible for {@code deviceInfo} can update the {@link TvInputInfo}
475      * object based on the updated {@code deviceInfo} (e.g. update the label based on the preferred
476      * device OSD name).
477      *
478      * @param deviceInfo the updated {@link HdmiDeviceInfo} object.
479      * @hide
480      */
481     @SystemApi
onHdmiDeviceUpdated(@onNull HdmiDeviceInfo deviceInfo)482     public void onHdmiDeviceUpdated(@NonNull HdmiDeviceInfo deviceInfo) {
483     }
484 
isPassthroughInput(String inputId)485     private boolean isPassthroughInput(String inputId) {
486         if (mTvInputManager == null) {
487             mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
488         }
489         TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
490         return info != null && info.isPassthroughInput();
491     }
492 
493     /**
494      * Base class for derived classes to implement to provide a TV input session.
495      */
496     public abstract static class Session implements KeyEvent.Callback {
497         private static final int POSITION_UPDATE_INTERVAL_MS = 1000;
498 
499         private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
500         private final WindowManager mWindowManager;
501         final Handler mHandler;
502         private WindowManager.LayoutParams mWindowParams;
503         private Surface mSurface;
504         private final Context mContext;
505         private FrameLayout mOverlayViewContainer;
506         private View mOverlayView;
507         private OverlayViewCleanUpTask mOverlayViewCleanUpTask;
508         private boolean mOverlayViewEnabled;
509         private IBinder mWindowToken;
510         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
511         private Rect mOverlayFrame;
512         private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
513         private long mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
514         private final TimeShiftPositionTrackingRunnable
515                 mTimeShiftPositionTrackingRunnable = new TimeShiftPositionTrackingRunnable();
516 
517         private final Object mLock = new Object();
518         // @GuardedBy("mLock")
519         private ITvInputSessionCallback mSessionCallback;
520         // @GuardedBy("mLock")
521         private final List<Runnable> mPendingActions = new ArrayList<>();
522 
523         /**
524          * Creates a new Session.
525          *
526          * @param context The context of the application
527          */
Session(Context context)528         public Session(Context context) {
529             mContext = context;
530             mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
531             mHandler = new Handler(context.getMainLooper());
532         }
533 
534         /**
535          * Enables or disables the overlay view.
536          *
537          * <p>By default, the overlay view is disabled. Must be called explicitly after the
538          * session is created to enable the overlay view.
539          *
540          * <p>The TV input service can disable its overlay view when the size of the overlay view is
541          * insufficient to display the whole information, such as when used in Picture-in-picture.
542          * Override {@link #onOverlayViewSizeChanged} to get the size of the overlay view, which
543          * then can be used to determine whether to enable/disable the overlay view.
544          *
545          * @param enable {@code true} if you want to enable the overlay view. {@code false}
546          *            otherwise.
547          */
setOverlayViewEnabled(final boolean enable)548         public void setOverlayViewEnabled(final boolean enable) {
549             mHandler.post(new Runnable() {
550                 @Override
551                 public void run() {
552                     if (enable == mOverlayViewEnabled) {
553                         return;
554                     }
555                     mOverlayViewEnabled = enable;
556                     if (enable) {
557                         if (mWindowToken != null) {
558                             createOverlayView(mWindowToken, mOverlayFrame);
559                         }
560                     } else {
561                         removeOverlayView(false);
562                     }
563                 }
564             });
565         }
566 
567         /**
568          * Dispatches an event to the application using this session.
569          *
570          * @param eventType The type of the event.
571          * @param eventArgs Optional arguments of the event.
572          * @hide
573          */
574         @SystemApi
notifySessionEvent(@onNull final String eventType, final Bundle eventArgs)575         public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) {
576             Preconditions.checkNotNull(eventType);
577             executeOrPostRunnableOnMainThread(new Runnable() {
578                 @Override
579                 public void run() {
580                     try {
581                         if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
582                         if (mSessionCallback != null) {
583                             mSessionCallback.onSessionEvent(eventType, eventArgs);
584                         }
585                     } catch (RemoteException e) {
586                         Log.w(TAG, "error in sending event (event=" + eventType + ")", e);
587                     }
588                 }
589             });
590         }
591 
592         /**
593          * Informs the application that the current channel is re-tuned for some reason and the
594          * session now displays the content from a new channel. This is used to handle special cases
595          * such as when the current channel becomes unavailable, it is necessary to send the user to
596          * a certain channel or the user changes channel in some other way (e.g. by using a
597          * dedicated remote).
598          *
599          * @param channelUri The URI of the new channel.
600          */
notifyChannelRetuned(final Uri channelUri)601         public void notifyChannelRetuned(final Uri channelUri) {
602             executeOrPostRunnableOnMainThread(new Runnable() {
603                 @MainThread
604                 @Override
605                 public void run() {
606                     try {
607                         if (DEBUG) Log.d(TAG, "notifyChannelRetuned");
608                         if (mSessionCallback != null) {
609                             mSessionCallback.onChannelRetuned(channelUri);
610                         }
611                     } catch (RemoteException e) {
612                         Log.w(TAG, "error in notifyChannelRetuned", e);
613                     }
614                 }
615             });
616         }
617 
618         /**
619          * Informs the application that this session has been tuned to the given channel.
620          *
621          * @param channelUri The URI of the tuned channel.
622          */
notifyTuned(@onNull Uri channelUri)623         public void notifyTuned(@NonNull Uri channelUri) {
624             executeOrPostRunnableOnMainThread(new Runnable() {
625                 @MainThread
626                 @Override
627                 public void run() {
628                     try {
629                         if (DEBUG) Log.d(TAG, "notifyTuned");
630                         if (mSessionCallback != null) {
631                             mSessionCallback.onTuned(channelUri);
632                         }
633                     } catch (RemoteException e) {
634                         Log.w(TAG, "error in notifyTuned", e);
635                     }
636                 }
637             });
638         }
639 
640         /**
641          * Sends the list of all audio/video/subtitle tracks. The is used by the framework to
642          * maintain the track information for a given session, which in turn is used by
643          * {@link TvView#getTracks} for the application to retrieve metadata for a given track type.
644          * The TV input service must call this method as soon as the track information becomes
645          * available or is updated. Note that in a case where a part of the information for a
646          * certain track is updated, it is not necessary to create a new {@link TvTrackInfo} object
647          * with a different track ID.
648          *
649          * @param tracks A list which includes track information.
650          */
notifyTracksChanged(final List<TvTrackInfo> tracks)651         public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
652             final List<TvTrackInfo> tracksCopy = new ArrayList<>(tracks);
653             executeOrPostRunnableOnMainThread(new Runnable() {
654                 @MainThread
655                 @Override
656                 public void run() {
657                     try {
658                         if (DEBUG) Log.d(TAG, "notifyTracksChanged");
659                         if (mSessionCallback != null) {
660                             mSessionCallback.onTracksChanged(tracksCopy);
661                         }
662                     } catch (RemoteException e) {
663                         Log.w(TAG, "error in notifyTracksChanged", e);
664                     }
665                 }
666             });
667         }
668 
669         /**
670          * Sends the type and ID of a selected track. This is used to inform the application that a
671          * specific track is selected. The TV input service must call this method as soon as a track
672          * is selected either by default or in response to a call to {@link #onSelectTrack}. The
673          * selected track ID for a given type is maintained in the framework until the next call to
674          * this method even after the entire track list is updated (but is reset when the session is
675          * tuned to a new channel), so care must be taken not to result in an obsolete track ID.
676          *
677          * @param type The type of the selected track. The type can be
678          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
679          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
680          * @param trackId The ID of the selected track.
681          * @see #onSelectTrack
682          */
notifyTrackSelected(final int type, final String trackId)683         public void notifyTrackSelected(final int type, final String trackId) {
684             executeOrPostRunnableOnMainThread(new Runnable() {
685                 @MainThread
686                 @Override
687                 public void run() {
688                     try {
689                         if (DEBUG) Log.d(TAG, "notifyTrackSelected");
690                         if (mSessionCallback != null) {
691                             mSessionCallback.onTrackSelected(type, trackId);
692                         }
693                     } catch (RemoteException e) {
694                         Log.w(TAG, "error in notifyTrackSelected", e);
695                     }
696                 }
697             });
698         }
699 
700         /**
701          * Informs the application that the video is now available for watching. Video is blocked
702          * until this method is called.
703          *
704          * <p>The TV input service must call this method as soon as the content rendered onto its
705          * surface is ready for viewing. This method must be called each time {@link #onTune}
706          * is called.
707          *
708          * @see #notifyVideoUnavailable
709          */
notifyVideoAvailable()710         public void notifyVideoAvailable() {
711             executeOrPostRunnableOnMainThread(new Runnable() {
712                 @MainThread
713                 @Override
714                 public void run() {
715                     try {
716                         if (DEBUG) Log.d(TAG, "notifyVideoAvailable");
717                         if (mSessionCallback != null) {
718                             mSessionCallback.onVideoAvailable();
719                         }
720                     } catch (RemoteException e) {
721                         Log.w(TAG, "error in notifyVideoAvailable", e);
722                     }
723                 }
724             });
725         }
726 
727         /**
728          * Informs the application that the video became unavailable for some reason. This is
729          * primarily used to signal the application to block the screen not to show any intermittent
730          * video artifacts.
731          *
732          * @param reason The reason why the video became unavailable:
733          *            <ul>
734          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
735          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
736          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
737          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
738          *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
739          *            </ul>
740          * @see #notifyVideoAvailable
741          */
notifyVideoUnavailable( @vInputManager.VideoUnavailableReason final int reason)742         public void notifyVideoUnavailable(
743                 @TvInputManager.VideoUnavailableReason final int reason) {
744             if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
745                     || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
746                 Log.e(TAG, "notifyVideoUnavailable - unknown reason: " + reason);
747             }
748             executeOrPostRunnableOnMainThread(new Runnable() {
749                 @MainThread
750                 @Override
751                 public void run() {
752                     try {
753                         if (DEBUG) Log.d(TAG, "notifyVideoUnavailable");
754                         if (mSessionCallback != null) {
755                             mSessionCallback.onVideoUnavailable(reason);
756                         }
757                     } catch (RemoteException e) {
758                         Log.w(TAG, "error in notifyVideoUnavailable", e);
759                     }
760                 }
761             });
762         }
763 
764         /**
765          * Sends an updated list of all audio presentations available from a Next Generation Audio
766          * service. This is used by the framework to maintain the audio presentation information for
767          * a given track of {@link TvTrackInfo#TYPE_AUDIO}, which in turn is used by
768          * {@link TvView#getAudioPresentations} for the application to retrieve metadata for the
769          * current audio track. The TV input service must call this method as soon as the audio
770          * track presentation information becomes available or is updated. Note that in a case
771          * where a part of the information for the current track is updated, it is not necessary
772          * to create a new {@link TvTrackInfo} object with a different track ID.
773          *
774          * @param audioPresentations A list of audio presentation information pertaining to the
775          * selected track.
776          */
notifyAudioPresentationChanged(@onNull final List<AudioPresentation> audioPresentations)777         public void notifyAudioPresentationChanged(@NonNull final List<AudioPresentation>
778                 audioPresentations) {
779             final List<AudioPresentation> ap = new ArrayList<>(audioPresentations);
780             executeOrPostRunnableOnMainThread(new Runnable() {
781                 @MainThread
782                 @Override
783                 public void run() {
784                     try {
785                         if (DEBUG) {
786                             Log.d(TAG, "notifyAudioPresentationsChanged");
787                         }
788                         if (mSessionCallback != null) {
789                             mSessionCallback.onAudioPresentationsChanged(ap);
790                         }
791                     } catch (RemoteException e) {
792                         Log.e(TAG, "error in notifyAudioPresentationsChanged", e);
793                     }
794                 }
795             });
796         }
797 
798         /**
799          * Sends the presentation and program IDs of the selected audio presentation. This is used
800          * to inform the application that a specific audio presentation is selected. The TV input
801          * service must call this method as soon as an audio presentation is selected either by
802          * default or in response to a call to {@link #onSelectTrack}. The selected audio
803          * presentation ID for a currently selected audio track is maintained in the framework until
804          * the next call to this method even after the entire audio presentation list for the track
805          * is updated (but is reset when the session is tuned to a new channel), so care must be
806          * taken not to result in an obsolete track audio presentation ID.
807          *
808          * @param presentationId The ID of the selected audio presentation for the current track.
809          * @param programId The ID of the program providing the selected audio presentation.
810          * @see #onSelectAudioPresentation
811          */
notifyAudioPresentationSelected(final int presentationId, final int programId)812         public void notifyAudioPresentationSelected(final int presentationId, final int programId) {
813             executeOrPostRunnableOnMainThread(new Runnable() {
814                 @MainThread
815                 @Override
816                 public void run() {
817                     try {
818                         if (DEBUG) {
819                             Log.d(TAG, "notifyAudioPresentationSelected");
820                         }
821                         if (mSessionCallback != null) {
822                             mSessionCallback.onAudioPresentationSelected(presentationId, programId);
823                         }
824                     } catch (RemoteException e) {
825                         Log.e(TAG, "error in notifyAudioPresentationSelected", e);
826                     }
827                 }
828             });
829         }
830 
831 
832         /**
833          * Informs the application that the user is allowed to watch the current program content.
834          *
835          * <p>Each TV input service is required to query the system whether the user is allowed to
836          * watch the current program before showing it to the user if the parental controls is
837          * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
838          * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
839          * service should block the content or not is determined by invoking
840          * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
841          * with the content rating for the current program. Then the {@link TvInputManager} makes a
842          * judgment based on the user blocked ratings stored in the secure settings and returns the
843          * result. If the rating in question turns out to be allowed by the user, the TV input
844          * service must call this method to notify the application that is permitted to show the
845          * content.
846          *
847          * <p>Each TV input service also needs to continuously listen to any changes made to the
848          * parental controls settings by registering a broadcast receiver to receive
849          * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
850          * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
851          * reevaluate the current program with the new parental controls settings.
852          *
853          * @see #notifyContentBlocked
854          * @see TvInputManager
855          */
notifyContentAllowed()856         public void notifyContentAllowed() {
857             executeOrPostRunnableOnMainThread(new Runnable() {
858                 @MainThread
859                 @Override
860                 public void run() {
861                     try {
862                         if (DEBUG) Log.d(TAG, "notifyContentAllowed");
863                         if (mSessionCallback != null) {
864                             mSessionCallback.onContentAllowed();
865                         }
866                     } catch (RemoteException e) {
867                         Log.w(TAG, "error in notifyContentAllowed", e);
868                     }
869                 }
870             });
871         }
872 
873         /**
874          * Informs the application that the current program content is blocked by parent controls.
875          *
876          * <p>Each TV input service is required to query the system whether the user is allowed to
877          * watch the current program before showing it to the user if the parental controls is
878          * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
879          * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
880          * service should block the content or not is determined by invoking
881          * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
882          * with the content rating for the current program or {@link TvContentRating#UNRATED} in
883          * case the rating information is missing. Then the {@link TvInputManager} makes a judgment
884          * based on the user blocked ratings stored in the secure settings and returns the result.
885          * If the rating in question turns out to be blocked, the TV input service must immediately
886          * block the content and call this method with the content rating of the current program to
887          * prompt the PIN verification screen.
888          *
889          * <p>Each TV input service also needs to continuously listen to any changes made to the
890          * parental controls settings by registering a broadcast receiver to receive
891          * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
892          * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
893          * reevaluate the current program with the new parental controls settings.
894          *
895          * @param rating The content rating for the current TV program. Can be
896          *            {@link TvContentRating#UNRATED}.
897          * @see #notifyContentAllowed
898          * @see TvInputManager
899          */
notifyContentBlocked(@onNull final TvContentRating rating)900         public void notifyContentBlocked(@NonNull final TvContentRating rating) {
901             Preconditions.checkNotNull(rating);
902             executeOrPostRunnableOnMainThread(new Runnable() {
903                 @MainThread
904                 @Override
905                 public void run() {
906                     try {
907                         if (DEBUG) Log.d(TAG, "notifyContentBlocked");
908                         if (mSessionCallback != null) {
909                             mSessionCallback.onContentBlocked(rating.flattenToString());
910                         }
911                     } catch (RemoteException e) {
912                         Log.w(TAG, "error in notifyContentBlocked", e);
913                     }
914                 }
915             });
916         }
917 
918         /**
919          * Informs the application that the time shift status is changed.
920          *
921          * <p>Prior to calling this method, the application assumes the status
922          * {@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}. Right after the session is created, it
923          * is important to invoke the method with the status
924          * {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} if the implementation does support
925          * time shifting, or {@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} otherwise. Failure
926          * to notifying the current status change immediately might result in an undesirable
927          * behavior in the application such as hiding the play controls.
928          *
929          * <p>If the status {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} is reported, the
930          * application assumes it can pause/resume playback, seek to a specified time position and
931          * set playback rate and audio mode. The implementation should override
932          * {@link #onTimeShiftPause}, {@link #onTimeShiftResume}, {@link #onTimeShiftSeekTo},
933          * {@link #onTimeShiftGetStartPosition}, {@link #onTimeShiftGetCurrentPosition} and
934          * {@link #onTimeShiftSetPlaybackParams}.
935          *
936          * @param status The current time shift status. Should be one of the followings.
937          * <ul>
938          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
939          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
940          * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
941          * </ul>
942          */
notifyTimeShiftStatusChanged(@vInputManager.TimeShiftStatus final int status)943         public void notifyTimeShiftStatusChanged(@TvInputManager.TimeShiftStatus final int status) {
944             executeOrPostRunnableOnMainThread(new Runnable() {
945                 @MainThread
946                 @Override
947                 public void run() {
948                     timeShiftEnablePositionTracking(
949                             status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE);
950                     try {
951                         if (DEBUG) Log.d(TAG, "notifyTimeShiftStatusChanged");
952                         if (mSessionCallback != null) {
953                             mSessionCallback.onTimeShiftStatusChanged(status);
954                         }
955                     } catch (RemoteException e) {
956                         Log.w(TAG, "error in notifyTimeShiftStatusChanged", e);
957                     }
958                 }
959             });
960         }
961 
962         /**
963          * Notifies response for broadcast info.
964          *
965          * @param response broadcast info response.
966          */
notifyBroadcastInfoResponse(@onNull final BroadcastInfoResponse response)967         public void notifyBroadcastInfoResponse(@NonNull final BroadcastInfoResponse response) {
968             executeOrPostRunnableOnMainThread(new Runnable() {
969                 @MainThread
970                 @Override
971                 public void run() {
972                     try {
973                         if (DEBUG) Log.d(TAG, "notifyBroadcastInfoResponse");
974                         if (mSessionCallback != null) {
975                             mSessionCallback.onBroadcastInfoResponse(response);
976                         }
977                     } catch (RemoteException e) {
978                         Log.w(TAG, "error in notifyBroadcastInfoResponse", e);
979                     }
980                 }
981             });
982         }
983 
984         /**
985          * Notifies response for advertisement.
986          *
987          * @param response advertisement response.
988          * @see android.media.tv.interactive.TvInteractiveAppService.Session#requestAd(AdRequest)
989          */
notifyAdResponse(@onNull final AdResponse response)990         public void notifyAdResponse(@NonNull final AdResponse response) {
991             executeOrPostRunnableOnMainThread(new Runnable() {
992                 @MainThread
993                 @Override
994                 public void run() {
995                     try {
996                         if (DEBUG) Log.d(TAG, "notifyAdResponse");
997                         if (mSessionCallback != null) {
998                             mSessionCallback.onAdResponse(response);
999                         }
1000                     } catch (RemoteException e) {
1001                         Log.w(TAG, "error in notifyAdResponse", e);
1002                     }
1003                 }
1004             });
1005         }
1006 
1007         /**
1008          * Notifies the advertisement buffer is consumed.
1009          *
1010          * @param buffer the {@link AdBuffer} that was consumed.
1011          */
notifyAdBufferConsumed(@onNull AdBuffer buffer)1012         public void notifyAdBufferConsumed(@NonNull AdBuffer buffer) {
1013             AdBuffer dupBuffer;
1014             try {
1015                 dupBuffer = AdBuffer.dupAdBuffer(buffer);
1016             } catch (IOException e) {
1017                 Log.w(TAG, "dup AdBuffer error in notifyAdBufferConsumed:", e);
1018                 return;
1019             }
1020             executeOrPostRunnableOnMainThread(new Runnable() {
1021                 @MainThread
1022                 @Override
1023                 public void run() {
1024                     try {
1025                         if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed");
1026                         if (mSessionCallback != null) {
1027                             mSessionCallback.onAdBufferConsumed(dupBuffer);
1028                         }
1029                     } catch (RemoteException e) {
1030                         Log.w(TAG, "error in notifyAdBufferConsumed", e);
1031                     } finally {
1032                         if (dupBuffer != null) {
1033                             dupBuffer.getSharedMemory().close();
1034                         }
1035                     }
1036                 }
1037             });
1038         }
1039 
1040         /**
1041          * Sends the raw data from the received TV message as well as the type of message received.
1042          *
1043          * @param type The of message that was sent, such as
1044          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
1045          * @param data The raw data of the message. The bundle keys are:
1046          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
1047          *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
1048          *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
1049          *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
1050          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
1051          *             how to parse this data.
1052          */
notifyTvMessage(@vInputManager.TvMessageType int type, @NonNull Bundle data)1053         public void notifyTvMessage(@TvInputManager.TvMessageType int type,
1054                 @NonNull Bundle data) {
1055             executeOrPostRunnableOnMainThread(new Runnable() {
1056                 @MainThread
1057                 @Override
1058                 public void run() {
1059                     try {
1060                         if (DEBUG) Log.d(TAG, "notifyTvMessage");
1061                         if (mSessionCallback != null) {
1062                             mSessionCallback.onTvMessage(type, data);
1063                         }
1064                     } catch (RemoteException e) {
1065                         Log.w(TAG, "error in notifyTvMessage", e);
1066                     }
1067                 }
1068             });
1069         }
1070 
notifyTimeShiftStartPositionChanged(final long timeMs)1071         private void notifyTimeShiftStartPositionChanged(final long timeMs) {
1072             executeOrPostRunnableOnMainThread(new Runnable() {
1073                 @MainThread
1074                 @Override
1075                 public void run() {
1076                     try {
1077                         if (DEBUG) Log.d(TAG, "notifyTimeShiftStartPositionChanged");
1078                         if (mSessionCallback != null) {
1079                             mSessionCallback.onTimeShiftStartPositionChanged(timeMs);
1080                         }
1081                     } catch (RemoteException e) {
1082                         Log.w(TAG, "error in notifyTimeShiftStartPositionChanged", e);
1083                     }
1084                 }
1085             });
1086         }
1087 
notifyTimeShiftCurrentPositionChanged(final long timeMs)1088         private void notifyTimeShiftCurrentPositionChanged(final long timeMs) {
1089             executeOrPostRunnableOnMainThread(new Runnable() {
1090                 @MainThread
1091                 @Override
1092                 public void run() {
1093                     try {
1094                         if (DEBUG) Log.d(TAG, "notifyTimeShiftCurrentPositionChanged");
1095                         if (mSessionCallback != null) {
1096                             mSessionCallback.onTimeShiftCurrentPositionChanged(timeMs);
1097                         }
1098                     } catch (RemoteException e) {
1099                         Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged", e);
1100                     }
1101                 }
1102             });
1103         }
1104 
1105         /**
1106          * Informs the app that the AIT (Application Information Table) is updated.
1107          *
1108          * <p>This method should also be called when
1109          * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT
1110          * info.
1111          *
1112          * @see #onSetInteractiveAppNotificationEnabled(boolean)
1113          */
notifyAitInfoUpdated(@onNull final AitInfo aitInfo)1114         public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) {
1115             executeOrPostRunnableOnMainThread(new Runnable() {
1116                 @MainThread
1117                 @Override
1118                 public void run() {
1119                     try {
1120                         if (DEBUG) Log.d(TAG, "notifyAitInfoUpdated");
1121                         if (mSessionCallback != null) {
1122                             mSessionCallback.onAitInfoUpdated(aitInfo);
1123                         }
1124                     } catch (RemoteException e) {
1125                         Log.w(TAG, "error in notifyAitInfoUpdated", e);
1126                     }
1127                 }
1128             });
1129         }
1130 
1131         /**
1132          * Informs the app that the time shift mode is set or updated.
1133          *
1134          * @param mode The current time shift mode. The value is one of the following:
1135          * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
1136          * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
1137          * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
1138          */
notifyTimeShiftMode(@ndroid.media.tv.TvInputManager.TimeShiftMode int mode)1139         public void notifyTimeShiftMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
1140             executeOrPostRunnableOnMainThread(new Runnable() {
1141                 @MainThread
1142                 @Override
1143                 public void run() {
1144                     try {
1145                         if (DEBUG) Log.d(TAG, "notifyTimeShiftMode");
1146                         if (mSessionCallback != null) {
1147                             mSessionCallback.onTimeShiftMode(mode);
1148                         }
1149                     } catch (RemoteException e) {
1150                         Log.w(TAG, "error in notifyTimeShiftMode", e);
1151                     }
1152                 }
1153             });
1154         }
1155 
1156         /**
1157          * Informs the app available speeds for time-shifting.
1158          * <p>This should be called when time-shifting is enabled.
1159          *
1160          * @param speeds An ordered array of playback speeds, expressed as values relative to the
1161          *               normal playback speed (1.0), at which the current content can be played as
1162          *               a time-shifted broadcast. This is an empty array if the supported playback
1163          *               speeds are unknown or the video/broadcast is not in time shift mode. If
1164          *               currently in time shift mode, this array will normally include at least
1165          *               the values 1.0 (normal speed) and 0.0 (paused).
1166          * @see PlaybackParams#getSpeed()
1167          */
notifyAvailableSpeeds(@onNull float[] speeds)1168         public void notifyAvailableSpeeds(@NonNull float[] speeds) {
1169             executeOrPostRunnableOnMainThread(new Runnable() {
1170                 @MainThread
1171                 @Override
1172                 public void run() {
1173                     try {
1174                         if (DEBUG) Log.d(TAG, "notifyAvailableSpeeds");
1175                         if (mSessionCallback != null) {
1176                             Arrays.sort(speeds);
1177                             mSessionCallback.onAvailableSpeeds(speeds);
1178                         }
1179                     } catch (RemoteException e) {
1180                         Log.w(TAG, "error in notifyAvailableSpeeds", e);
1181                     }
1182                 }
1183             });
1184         }
1185 
1186         /**
1187          * Notifies signal strength.
1188          */
notifySignalStrength(@vInputManager.SignalStrength final int strength)1189         public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) {
1190             executeOrPostRunnableOnMainThread(new Runnable() {
1191                 @MainThread
1192                 @Override
1193                 public void run() {
1194                     try {
1195                         if (DEBUG) Log.d(TAG, "notifySignalStrength");
1196                         if (mSessionCallback != null) {
1197                             mSessionCallback.onSignalStrength(strength);
1198                         }
1199                     } catch (RemoteException e) {
1200                         Log.w(TAG, "error in notifySignalStrength", e);
1201                     }
1202                 }
1203             });
1204         }
1205 
1206         /**
1207          * Informs the application that cueing message is available or unavailable.
1208          *
1209          * <p>The cueing message is used for digital program insertion, based on the standard
1210          * ANSI/SCTE 35 2019r1.
1211          *
1212          * @param available {@code true} if cueing message is available; {@code false} if it becomes
1213          *                  unavailable.
1214          */
notifyCueingMessageAvailability(boolean available)1215         public void notifyCueingMessageAvailability(boolean available) {
1216             executeOrPostRunnableOnMainThread(new Runnable() {
1217                 @MainThread
1218                 @Override
1219                 public void run() {
1220                     try {
1221                         if (DEBUG) Log.d(TAG, "notifyCueingMessageAvailability");
1222                         if (mSessionCallback != null) {
1223                             mSessionCallback.onCueingMessageAvailability(available);
1224                         }
1225                     } catch (RemoteException e) {
1226                         Log.w(TAG, "error in notifyCueingMessageAvailability", e);
1227                     }
1228                 }
1229             });
1230         }
1231 
1232         /**
1233          * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
1234          * is relative to the overlay view that sits on top of this surface.
1235          *
1236          * @param left Left position in pixels, relative to the overlay view.
1237          * @param top Top position in pixels, relative to the overlay view.
1238          * @param right Right position in pixels, relative to the overlay view.
1239          * @param bottom Bottom position in pixels, relative to the overlay view.
1240          * @see #onOverlayViewSizeChanged
1241          */
layoutSurface(final int left, final int top, final int right, final int bottom)1242         public void layoutSurface(final int left, final int top, final int right,
1243                 final int bottom) {
1244             if (left > right || top > bottom) {
1245                 throw new IllegalArgumentException("Invalid parameter");
1246             }
1247             executeOrPostRunnableOnMainThread(new Runnable() {
1248                 @MainThread
1249                 @Override
1250                 public void run() {
1251                     try {
1252                         if (DEBUG) Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top + ", r="
1253                                 + right + ", b=" + bottom + ",)");
1254                         if (mSessionCallback != null) {
1255                             mSessionCallback.onLayoutSurface(left, top, right, bottom);
1256                         }
1257                     } catch (RemoteException e) {
1258                         Log.w(TAG, "error in layoutSurface", e);
1259                     }
1260                 }
1261             });
1262         }
1263 
1264         /**
1265          * Called when the session is released.
1266          */
onRelease()1267         public abstract void onRelease();
1268 
1269         /**
1270          * Sets the current session as the main session. The main session is a session whose
1271          * corresponding TV input determines the HDMI-CEC active source device.
1272          *
1273          * <p>TV input service that manages HDMI-CEC logical device should implement {@link
1274          * #onSetMain} to (1) select the corresponding HDMI logical device as the source device
1275          * when {@code isMain} is {@code true}, and to (2) select the internal device (= TV itself)
1276          * as the source device when {@code isMain} is {@code false} and the session is still main.
1277          * Also, if a surface is passed to a non-main session and active source is changed to
1278          * initiate the surface, the active source should be returned to the main session.
1279          *
1280          * <p>{@link TvView} guarantees that, when tuning involves a session transition, {@code
1281          * onSetMain(true)} for new session is called first, {@code onSetMain(false)} for old
1282          * session is called afterwards. This allows {@code onSetMain(false)} to be no-op when TV
1283          * input service knows that the next main session corresponds to another HDMI logical
1284          * device. Practically, this implies that one TV input service should handle all HDMI port
1285          * and HDMI-CEC logical devices for smooth active source transition.
1286          *
1287          * @param isMain If true, session should become main.
1288          * @see TvView#setMain
1289          * @hide
1290          */
1291         @SystemApi
onSetMain(boolean isMain)1292         public void onSetMain(boolean isMain) {
1293         }
1294 
1295         /**
1296          * Called when the application sets the surface.
1297          *
1298          * <p>The TV input service should render video onto the given surface. When called with
1299          * {@code null}, the input service should immediately free any references to the
1300          * currently set surface and stop using it.
1301          *
1302          * @param surface The surface to be used for video rendering. Can be {@code null}.
1303          * @return {@code true} if the surface was set successfully, {@code false} otherwise.
1304          */
onSetSurface(@ullable Surface surface)1305         public abstract boolean onSetSurface(@Nullable Surface surface);
1306 
1307         /**
1308          * Called after any structural changes (format or size) have been made to the surface passed
1309          * in {@link #onSetSurface}. This method is always called at least once, after
1310          * {@link #onSetSurface} is called with non-null surface.
1311          *
1312          * @param format The new PixelFormat of the surface.
1313          * @param width The new width of the surface.
1314          * @param height The new height of the surface.
1315          */
onSurfaceChanged(int format, int width, int height)1316         public void onSurfaceChanged(int format, int width, int height) {
1317         }
1318 
1319         /**
1320          * Called when the size of the overlay view is changed by the application.
1321          *
1322          * <p>This is always called at least once when the session is created regardless of whether
1323          * the overlay view is enabled or not. The overlay view size is the same as the containing
1324          * {@link TvView}. Note that the size of the underlying surface can be different if the
1325          * surface was changed by calling {@link #layoutSurface}.
1326          *
1327          * @param width The width of the overlay view.
1328          * @param height The height of the overlay view.
1329          */
onOverlayViewSizeChanged(int width, int height)1330         public void onOverlayViewSizeChanged(int width, int height) {
1331         }
1332 
1333         /**
1334          * Sets the relative stream volume of the current TV input session.
1335          *
1336          * <p>The implementation should honor this request in order to handle audio focus changes or
1337          * mute the current session when multiple sessions, possibly from different inputs are
1338          * active. If the method has not yet been called, the implementation should assume the
1339          * default value of {@code 1.0f}.
1340          *
1341          * @param volume A volume value between {@code 0.0f} to {@code 1.0f}.
1342          */
onSetStreamVolume(@loatRangefrom = 0.0, to = 1.0) float volume)1343         public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume);
1344 
1345         /**
1346          * Called when broadcast info is requested.
1347          *
1348          * @param request broadcast info request
1349          */
onRequestBroadcastInfo(@onNull BroadcastInfoRequest request)1350         public void onRequestBroadcastInfo(@NonNull BroadcastInfoRequest request) {
1351         }
1352 
1353         /**
1354          * Called when broadcast info is removed.
1355          */
onRemoveBroadcastInfo(int requestId)1356         public void onRemoveBroadcastInfo(int requestId) {
1357         }
1358 
1359         /**
1360          * Called when advertisement request is received.
1361          *
1362          * @param request advertisement request received
1363          */
onRequestAd(@onNull AdRequest request)1364         public void onRequestAd(@NonNull AdRequest request) {
1365         }
1366 
1367         /**
1368          * Called when an advertisement buffer is ready for playback.
1369          *
1370          * @param buffer The {@link AdBuffer} that became ready for playback.
1371          */
onAdBufferReady(@onNull AdBuffer buffer)1372         public void onAdBufferReady(@NonNull AdBuffer buffer) {
1373         }
1374 
1375         /**
1376          * Tunes to a given channel.
1377          *
1378          * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called.
1379          * Also, {@link #notifyVideoUnavailable(int)} should be called when the TV input cannot
1380          * continue playing the given channel.
1381          *
1382          * @param channelUri The URI of the channel.
1383          * @return {@code true} if the tuning was successful, {@code false} otherwise.
1384          */
onTune(Uri channelUri)1385         public abstract boolean onTune(Uri channelUri);
1386 
1387         /**
1388          * Tunes to a given channel. Override this method in order to handle domain-specific
1389          * features that are only known between certain TV inputs and their clients.
1390          *
1391          * <p>The default implementation calls {@link #onTune(Uri)}.
1392          *
1393          * @param channelUri The URI of the channel.
1394          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
1395          *            name, i.e. prefixed with a package name you own, so that different developers
1396          *            will not create conflicting keys.
1397          * @return {@code true} if the tuning was successful, {@code false} otherwise.
1398          */
onTune(Uri channelUri, Bundle params)1399         public boolean onTune(Uri channelUri, Bundle params) {
1400             return onTune(channelUri);
1401         }
1402 
1403         /**
1404          * Enables or disables the caption.
1405          *
1406          * <p>The locale for the user's preferred captioning language can be obtained by calling
1407          * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
1408          *
1409          * @param enabled {@code true} to enable, {@code false} to disable.
1410          * @see CaptioningManager
1411          */
onSetCaptionEnabled(boolean enabled)1412         public abstract void onSetCaptionEnabled(boolean enabled);
1413 
1414         /**
1415          * Requests to unblock the content according to the given rating.
1416          *
1417          * <p>The implementation should unblock the content.
1418          * TV input service has responsibility to decide when/how the unblock expires
1419          * while it can keep previously unblocked ratings in order not to ask a user
1420          * to unblock whenever a content rating is changed.
1421          * Therefore an unblocked rating can be valid for a channel, a program,
1422          * or certain amount of time depending on the implementation.
1423          *
1424          * @param unblockedRating An unblocked content rating
1425          */
onUnblockContent(TvContentRating unblockedRating)1426         public void onUnblockContent(TvContentRating unblockedRating) {
1427         }
1428 
1429         /**
1430          * Selects a given track.
1431          *
1432          * <p>If this is done successfully, the implementation should call
1433          * {@link #notifyTrackSelected} to help applications maintain the up-to-date list of the
1434          * selected tracks.
1435          *
1436          * @param trackId The ID of the track to select. {@code null} means to unselect the current
1437          *            track for a given type.
1438          * @param type The type of the track to select. The type can be
1439          *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
1440          *            {@link TvTrackInfo#TYPE_SUBTITLE}.
1441          * @return {@code true} if the track selection was successful, {@code false} otherwise.
1442          * @see #notifyTrackSelected
1443          */
onSelectTrack(int type, @Nullable String trackId)1444         public boolean onSelectTrack(int type, @Nullable String trackId) {
1445             return false;
1446         }
1447 
1448         /**
1449          * Enables or disables interactive app notification.
1450          *
1451          * <p>This method enables or disables the event detection from the corresponding TV input.
1452          * When it's enabled, the TV input service detects events related to interactive app, such
1453          * as AIT (Application Information Table) and sends to TvView or the linked TV interactive
1454          * app service.
1455          *
1456          * @param enabled {@code true} to enable, {@code false} to disable.
1457          *
1458          * @see TvView#setInteractiveAppNotificationEnabled(boolean)
1459          * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo)
1460          */
onSetInteractiveAppNotificationEnabled(boolean enabled)1461         public void onSetInteractiveAppNotificationEnabled(boolean enabled) {
1462         }
1463 
1464         /**
1465          * Selects an audio presentation.
1466          *
1467          * <p>On successfully selecting the audio presentation,
1468          * {@link #notifyAudioPresentationSelected} is invoked to provide updated information about
1469          * the selected audio presentation to applications.
1470          *
1471          * @param presentationId The ID of the audio presentation to select.
1472          * @param programId The ID of the program providing the selected audio presentation.
1473          * @return {@code true} if the audio presentation selection was successful,
1474          *         {@code false} otherwise.
1475          * @see #notifyAudioPresentationSelected
1476          */
onSelectAudioPresentation(int presentationId, int programId)1477         public boolean onSelectAudioPresentation(int presentationId, int programId) {
1478             return false;
1479         }
1480 
1481         /**
1482          * Processes a private command sent from the application to the TV input. This can be used
1483          * to provide domain-specific features that are only known between certain TV inputs and
1484          * their clients.
1485          *
1486          * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
1487          *            i.e. prefixed with a package name you own, so that different developers will
1488          *            not create conflicting commands.
1489          * @param data Any data to include with the command.
1490          */
onAppPrivateCommand(@onNull String action, Bundle data)1491         public void onAppPrivateCommand(@NonNull String action, Bundle data) {
1492         }
1493 
1494         /**
1495          * Called when the application requests to create an overlay view. Each session
1496          * implementation can override this method and return its own view.
1497          *
1498          * @return a view attached to the overlay window
1499          */
onCreateOverlayView()1500         public View onCreateOverlayView() {
1501             return null;
1502         }
1503 
1504         /**
1505          * Called when the application enables or disables the detection of the specified message
1506          * type.
1507          * @param type The type of message received, such as
1508          *             {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
1509          * @param enabled {@code true} if TV message detection is enabled,
1510          *                {@code false} otherwise.
1511          */
onSetTvMessageEnabled(@vInputManager.TvMessageType int type, boolean enabled)1512         public void onSetTvMessageEnabled(@TvInputManager.TvMessageType int type,
1513                 boolean enabled) {
1514         }
1515 
1516         /**
1517          * Called when a TV message is received
1518          *
1519          * @param type The type of message received, such as
1520          * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK}
1521          * @param data The raw data of the message. The bundle keys are:
1522          *             {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID},
1523          *             {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID},
1524          *             {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE},
1525          *             {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}.
1526          *             See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on
1527          *             how to parse this data.
1528          */
onTvMessage(@vInputManager.TvMessageType int type, @NonNull Bundle data)1529         public void onTvMessage(@TvInputManager.TvMessageType int type,
1530                 @NonNull Bundle data) {
1531         }
1532 
1533         /**
1534          * Called when the application requests to play a given recorded TV program.
1535          *
1536          * @param recordedProgramUri The URI of a recorded TV program.
1537          * @see #onTimeShiftResume()
1538          * @see #onTimeShiftPause()
1539          * @see #onTimeShiftSeekTo(long)
1540          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
1541          * @see #onTimeShiftGetStartPosition()
1542          * @see #onTimeShiftGetCurrentPosition()
1543          */
onTimeShiftPlay(Uri recordedProgramUri)1544         public void onTimeShiftPlay(Uri recordedProgramUri) {
1545         }
1546 
1547         /**
1548          * Called when the application requests to pause playback.
1549          *
1550          * @see #onTimeShiftPlay(Uri)
1551          * @see #onTimeShiftResume()
1552          * @see #onTimeShiftSeekTo(long)
1553          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
1554          * @see #onTimeShiftGetStartPosition()
1555          * @see #onTimeShiftGetCurrentPosition()
1556          */
onTimeShiftPause()1557         public void onTimeShiftPause() {
1558         }
1559 
1560         /**
1561          * Called when the application requests to resume playback.
1562          *
1563          * @see #onTimeShiftPlay(Uri)
1564          * @see #onTimeShiftPause()
1565          * @see #onTimeShiftSeekTo(long)
1566          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
1567          * @see #onTimeShiftGetStartPosition()
1568          * @see #onTimeShiftGetCurrentPosition()
1569          */
onTimeShiftResume()1570         public void onTimeShiftResume() {
1571         }
1572 
1573         /**
1574          * Called when the application requests to seek to a specified time position. Normally, the
1575          * position is given within range between the start and the current time, inclusively. The
1576          * implementation is expected to seek to the nearest time position if the given position is
1577          * not in the range.
1578          *
1579          * @param timeMs The time position to seek to, in milliseconds since the epoch.
1580          * @see #onTimeShiftPlay(Uri)
1581          * @see #onTimeShiftResume()
1582          * @see #onTimeShiftPause()
1583          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
1584          * @see #onTimeShiftGetStartPosition()
1585          * @see #onTimeShiftGetCurrentPosition()
1586          */
onTimeShiftSeekTo(long timeMs)1587         public void onTimeShiftSeekTo(long timeMs) {
1588         }
1589 
1590         /**
1591          * Called when the application sets playback parameters containing the speed and audio mode.
1592          *
1593          * <p>Once the playback parameters are set, the implementation should honor the current
1594          * settings until the next tune request. Pause/resume/seek request does not reset the
1595          * parameters previously set.
1596          *
1597          * @param params The playback params.
1598          * @see #onTimeShiftPlay(Uri)
1599          * @see #onTimeShiftResume()
1600          * @see #onTimeShiftPause()
1601          * @see #onTimeShiftSeekTo(long)
1602          * @see #onTimeShiftGetStartPosition()
1603          * @see #onTimeShiftGetCurrentPosition()
1604          */
onTimeShiftSetPlaybackParams(PlaybackParams params)1605         public void onTimeShiftSetPlaybackParams(PlaybackParams params) {
1606         }
1607 
1608         /**
1609          * Called when the application sets time shift mode.
1610          *
1611          * @param mode The time shift mode. The value is one of the following:
1612          * {@link TvInputManager#TIME_SHIFT_MODE_OFF}, {@link TvInputManager#TIME_SHIFT_MODE_LOCAL},
1613          * {@link TvInputManager#TIME_SHIFT_MODE_NETWORK},
1614          * {@link TvInputManager#TIME_SHIFT_MODE_AUTO}.
1615          */
onTimeShiftSetMode(@ndroid.media.tv.TvInputManager.TimeShiftMode int mode)1616         public void onTimeShiftSetMode(@android.media.tv.TvInputManager.TimeShiftMode int mode) {
1617         }
1618 
1619         /**
1620          * Returns the start position for time shifting, in milliseconds since the epoch.
1621          * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
1622          * moment.
1623          *
1624          * <p>The start position for time shifting indicates the earliest possible time the user can
1625          * seek to. Initially this is equivalent to the time when the implementation starts
1626          * recording. Later it may be adjusted because there is insufficient space or the duration
1627          * of recording is limited by the implementation. The application does not allow the user to
1628          * seek to a position earlier than the start position.
1629          *
1630          * <p>For playback of a recorded program initiated by {@link #onTimeShiftPlay(Uri)}, the
1631          * start position should be 0 and does not change.
1632          *
1633          * @see #onTimeShiftPlay(Uri)
1634          * @see #onTimeShiftResume()
1635          * @see #onTimeShiftPause()
1636          * @see #onTimeShiftSeekTo(long)
1637          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
1638          * @see #onTimeShiftGetCurrentPosition()
1639          */
onTimeShiftGetStartPosition()1640         public long onTimeShiftGetStartPosition() {
1641             return TvInputManager.TIME_SHIFT_INVALID_TIME;
1642         }
1643 
1644         /**
1645          * Returns the current position for time shifting, in milliseconds since the epoch.
1646          * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
1647          * moment.
1648          *
1649          * <p>The current position for time shifting is the same as the current position of
1650          * playback. It should be equal to or greater than the start position reported by
1651          * {@link #onTimeShiftGetStartPosition()}. When playback is completed, the current position
1652          * should stay where the playback ends, in other words, the returned value of this mehtod
1653          * should be equal to the start position plus the duration of the program.
1654          *
1655          * @see #onTimeShiftPlay(Uri)
1656          * @see #onTimeShiftResume()
1657          * @see #onTimeShiftPause()
1658          * @see #onTimeShiftSeekTo(long)
1659          * @see #onTimeShiftSetPlaybackParams(PlaybackParams)
1660          * @see #onTimeShiftGetStartPosition()
1661          */
onTimeShiftGetCurrentPosition()1662         public long onTimeShiftGetCurrentPosition() {
1663             return TvInputManager.TIME_SHIFT_INVALID_TIME;
1664         }
1665 
1666         /**
1667          * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
1668          * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
1669          *
1670          * <p>Override this to intercept key down events before they are processed by the
1671          * application. If you return true, the application will not process the event itself. If
1672          * you return false, the normal application processing will occur as if the TV input had not
1673          * seen the event at all.
1674          *
1675          * @param keyCode The value in event.getKeyCode().
1676          * @param event Description of the key event.
1677          * @return If you handled the event, return {@code true}. If you want to allow the event to
1678          *         be handled by the next receiver, return {@code false}.
1679          */
1680         @Override
onKeyDown(int keyCode, KeyEvent event)1681         public boolean onKeyDown(int keyCode, KeyEvent event) {
1682             return false;
1683         }
1684 
1685         /**
1686          * Default implementation of
1687          * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
1688          * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
1689          *
1690          * <p>Override this to intercept key long press events before they are processed by the
1691          * application. If you return true, the application will not process the event itself. If
1692          * you return false, the normal application processing will occur as if the TV input had not
1693          * seen the event at all.
1694          *
1695          * @param keyCode The value in event.getKeyCode().
1696          * @param event Description of the key event.
1697          * @return If you handled the event, return {@code true}. If you want to allow the event to
1698          *         be handled by the next receiver, return {@code false}.
1699          */
1700         @Override
onKeyLongPress(int keyCode, KeyEvent event)1701         public boolean onKeyLongPress(int keyCode, KeyEvent event) {
1702             return false;
1703         }
1704 
1705         /**
1706          * Default implementation of
1707          * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
1708          * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
1709          *
1710          * <p>Override this to intercept special key multiple events before they are processed by
1711          * the application. If you return true, the application will not itself process the event.
1712          * If you return false, the normal application processing will occur as if the TV input had
1713          * not seen the event at all.
1714          *
1715          * @param keyCode The value in event.getKeyCode().
1716          * @param count The number of times the action was made.
1717          * @param event Description of the key event.
1718          * @return If you handled the event, return {@code true}. If you want to allow the event to
1719          *         be handled by the next receiver, return {@code false}.
1720          */
1721         @Override
onKeyMultiple(int keyCode, int count, KeyEvent event)1722         public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
1723             return false;
1724         }
1725 
1726         /**
1727          * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
1728          * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
1729          *
1730          * <p>Override this to intercept key up events before they are processed by the application.
1731          * If you return true, the application will not itself process the event. If you return false,
1732          * the normal application processing will occur as if the TV input had not seen the event at
1733          * all.
1734          *
1735          * @param keyCode The value in event.getKeyCode().
1736          * @param event Description of the key event.
1737          * @return If you handled the event, return {@code true}. If you want to allow the event to
1738          *         be handled by the next receiver, return {@code false}.
1739          */
1740         @Override
onKeyUp(int keyCode, KeyEvent event)1741         public boolean onKeyUp(int keyCode, KeyEvent event) {
1742             return false;
1743         }
1744 
1745         /**
1746          * Implement this method to handle touch screen motion events on the current input session.
1747          *
1748          * @param event The motion event being received.
1749          * @return If you handled the event, return {@code true}. If you want to allow the event to
1750          *         be handled by the next receiver, return {@code false}.
1751          * @see View#onTouchEvent
1752          */
onTouchEvent(MotionEvent event)1753         public boolean onTouchEvent(MotionEvent event) {
1754             return false;
1755         }
1756 
1757         /**
1758          * Implement this method to handle trackball events on the current input session.
1759          *
1760          * @param event The motion event being received.
1761          * @return If you handled the event, return {@code true}. If you want to allow the event to
1762          *         be handled by the next receiver, return {@code false}.
1763          * @see View#onTrackballEvent
1764          */
onTrackballEvent(MotionEvent event)1765         public boolean onTrackballEvent(MotionEvent event) {
1766             return false;
1767         }
1768 
1769         /**
1770          * Implement this method to handle generic motion events on the current input session.
1771          *
1772          * @param event The motion event being received.
1773          * @return If you handled the event, return {@code true}. If you want to allow the event to
1774          *         be handled by the next receiver, return {@code false}.
1775          * @see View#onGenericMotionEvent
1776          */
onGenericMotionEvent(MotionEvent event)1777         public boolean onGenericMotionEvent(MotionEvent event) {
1778             return false;
1779         }
1780 
1781         /**
1782          * This method is called when the application would like to stop using the current input
1783          * session.
1784          */
release()1785         void release() {
1786             onRelease();
1787             if (mSurface != null) {
1788                 mSurface.release();
1789                 mSurface = null;
1790             }
1791             synchronized(mLock) {
1792                 mSessionCallback = null;
1793                 mPendingActions.clear();
1794             }
1795             // Removes the overlay view lastly so that any hanging on the main thread can be handled
1796             // in {@link #scheduleOverlayViewCleanup}.
1797             removeOverlayView(true);
1798             mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
1799         }
1800 
1801         /**
1802          * Calls {@link #onSetMain}.
1803          */
setMain(boolean isMain)1804         void setMain(boolean isMain) {
1805             onSetMain(isMain);
1806         }
1807 
1808         /**
1809          * Calls {@link #onSetSurface}.
1810          */
setSurface(Surface surface)1811         void setSurface(Surface surface) {
1812             onSetSurface(surface);
1813             if (mSurface != null) {
1814                 mSurface.release();
1815             }
1816             mSurface = surface;
1817             // TODO: Handle failure.
1818         }
1819 
1820         /**
1821          * Calls {@link #onSurfaceChanged}.
1822          */
dispatchSurfaceChanged(int format, int width, int height)1823         void dispatchSurfaceChanged(int format, int width, int height) {
1824             if (DEBUG) {
1825                 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
1826                         + ", height=" + height + ")");
1827             }
1828             onSurfaceChanged(format, width, height);
1829         }
1830 
1831         /**
1832          * Calls {@link #onSetStreamVolume}.
1833          */
setStreamVolume(float volume)1834         void setStreamVolume(float volume) {
1835             onSetStreamVolume(volume);
1836         }
1837 
1838         /**
1839          * Calls {@link #onTune(Uri, Bundle)}.
1840          */
tune(Uri channelUri, Bundle params)1841         void tune(Uri channelUri, Bundle params) {
1842             mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
1843             onTune(channelUri, params);
1844             // TODO: Handle failure.
1845         }
1846 
1847         /**
1848          * Calls {@link #onSetCaptionEnabled}.
1849          */
setCaptionEnabled(boolean enabled)1850         void setCaptionEnabled(boolean enabled) {
1851             onSetCaptionEnabled(enabled);
1852         }
1853 
1854         /**
1855          * Calls {@link #onSelectAudioPresentation}.
1856          */
selectAudioPresentation(int presentationId, int programId)1857         void selectAudioPresentation(int presentationId, int programId) {
1858             onSelectAudioPresentation(presentationId, programId);
1859         }
1860 
1861         /**
1862          * Calls {@link #onSelectTrack}.
1863          */
selectTrack(int type, String trackId)1864         void selectTrack(int type, String trackId) {
1865             onSelectTrack(type, trackId);
1866         }
1867 
1868         /**
1869          * Calls {@link #onUnblockContent}.
1870          */
unblockContent(String unblockedRating)1871         void unblockContent(String unblockedRating) {
1872             onUnblockContent(TvContentRating.unflattenFromString(unblockedRating));
1873             // TODO: Handle failure.
1874         }
1875 
1876         /**
1877          * Calls {@link #onSetInteractiveAppNotificationEnabled}.
1878          */
setInteractiveAppNotificationEnabled(boolean enabled)1879         void setInteractiveAppNotificationEnabled(boolean enabled) {
1880             onSetInteractiveAppNotificationEnabled(enabled);
1881         }
1882 
1883         /**
1884          * Calls {@link #onSetTvMessageEnabled(int, boolean)}.
1885          */
setTvMessageEnabled(int type, boolean enabled)1886         void setTvMessageEnabled(int type, boolean enabled) {
1887             onSetTvMessageEnabled(type, enabled);
1888         }
1889 
1890         /**
1891          * Calls {@link #onAppPrivateCommand}.
1892          */
appPrivateCommand(String action, Bundle data)1893         void appPrivateCommand(String action, Bundle data) {
1894             onAppPrivateCommand(action, data);
1895         }
1896 
1897         /**
1898          * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
1899          * to the overlay window.
1900          *
1901          * @param windowToken A window token of the application.
1902          * @param frame A position of the overlay view.
1903          */
createOverlayView(IBinder windowToken, Rect frame)1904         void createOverlayView(IBinder windowToken, Rect frame) {
1905             if (mOverlayViewContainer != null) {
1906                 removeOverlayView(false);
1907             }
1908             if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
1909             mWindowToken = windowToken;
1910             mOverlayFrame = frame;
1911             onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
1912             if (!mOverlayViewEnabled) {
1913                 return;
1914             }
1915             mOverlayView = onCreateOverlayView();
1916             if (mOverlayView == null) {
1917                 return;
1918             }
1919             if (mOverlayViewCleanUpTask != null) {
1920                 mOverlayViewCleanUpTask.cancel(true);
1921                 mOverlayViewCleanUpTask = null;
1922             }
1923             // Creates a container view to check hanging on the overlay view detaching.
1924             // Adding/removing the overlay view to/from the container make the view attach/detach
1925             // logic run on the main thread.
1926             mOverlayViewContainer = new FrameLayout(mContext.getApplicationContext());
1927             mOverlayViewContainer.addView(mOverlayView);
1928             // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
1929             // an overlay window above the media window but below the application window.
1930             int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
1931             // We make the overlay view non-focusable and non-touchable so that
1932             // the application that owns the window token can decide whether to consume or
1933             // dispatch the input events.
1934             int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1935                     | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
1936                     | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
1937             if (ActivityManager.isHighEndGfx()) {
1938                 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
1939             }
1940             mWindowParams = new WindowManager.LayoutParams(
1941                     frame.right - frame.left, frame.bottom - frame.top,
1942                     frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT);
1943             mWindowParams.privateFlags |=
1944                     WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
1945             mWindowParams.gravity = Gravity.START | Gravity.TOP;
1946             mWindowParams.token = windowToken;
1947             mWindowManager.addView(mOverlayViewContainer, mWindowParams);
1948         }
1949 
1950         /**
1951          * Relayouts the current overlay view.
1952          *
1953          * @param frame A new position of the overlay view.
1954          */
relayoutOverlayView(Rect frame)1955         void relayoutOverlayView(Rect frame) {
1956             if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
1957             if (mOverlayFrame == null || mOverlayFrame.width() != frame.width()
1958                     || mOverlayFrame.height() != frame.height()) {
1959                 // Note: relayoutOverlayView is called whenever TvView's layout is changed
1960                 // regardless of setOverlayViewEnabled.
1961                 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
1962             }
1963             mOverlayFrame = frame;
1964             if (!mOverlayViewEnabled || mOverlayViewContainer == null) {
1965                 return;
1966             }
1967             mWindowParams.x = frame.left;
1968             mWindowParams.y = frame.top;
1969             mWindowParams.width = frame.right - frame.left;
1970             mWindowParams.height = frame.bottom - frame.top;
1971             mWindowManager.updateViewLayout(mOverlayViewContainer, mWindowParams);
1972         }
1973 
1974         /**
1975          * Removes the current overlay view.
1976          */
removeOverlayView(boolean clearWindowToken)1977         void removeOverlayView(boolean clearWindowToken) {
1978             if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayViewContainer + ")");
1979             if (clearWindowToken) {
1980                 mWindowToken = null;
1981                 mOverlayFrame = null;
1982             }
1983             if (mOverlayViewContainer != null) {
1984                 // Removes the overlay view from the view hierarchy in advance so that it can be
1985                 // cleaned up in the {@link OverlayViewCleanUpTask} if the remove process is
1986                 // hanging.
1987                 mOverlayViewContainer.removeView(mOverlayView);
1988                 mOverlayView = null;
1989                 mWindowManager.removeView(mOverlayViewContainer);
1990                 mOverlayViewContainer = null;
1991                 mWindowParams = null;
1992             }
1993         }
1994 
1995         /**
1996          * Calls {@link #onTimeShiftPlay(Uri)}.
1997          */
timeShiftPlay(Uri recordedProgramUri)1998         void timeShiftPlay(Uri recordedProgramUri) {
1999             mCurrentPositionMs = 0;
2000             onTimeShiftPlay(recordedProgramUri);
2001         }
2002 
2003         /**
2004          * Calls {@link #onTimeShiftPause}.
2005          */
timeShiftPause()2006         void timeShiftPause() {
2007             onTimeShiftPause();
2008         }
2009 
2010         /**
2011          * Calls {@link #onTimeShiftResume}.
2012          */
timeShiftResume()2013         void timeShiftResume() {
2014             onTimeShiftResume();
2015         }
2016 
2017         /**
2018          * Calls {@link #onTimeShiftSeekTo}.
2019          */
timeShiftSeekTo(long timeMs)2020         void timeShiftSeekTo(long timeMs) {
2021             onTimeShiftSeekTo(timeMs);
2022         }
2023 
2024         /**
2025          * Calls {@link #onTimeShiftSetPlaybackParams}.
2026          */
timeShiftSetPlaybackParams(PlaybackParams params)2027         void timeShiftSetPlaybackParams(PlaybackParams params) {
2028             onTimeShiftSetPlaybackParams(params);
2029         }
2030 
2031         /**
2032          * Calls {@link #onTimeShiftSetMode}.
2033          */
timeShiftSetMode(int mode)2034         void timeShiftSetMode(int mode) {
2035             onTimeShiftSetMode(mode);
2036         }
2037 
2038         /**
2039          * Enable/disable position tracking.
2040          *
2041          * @param enable {@code true} to enable tracking, {@code false} otherwise.
2042          */
timeShiftEnablePositionTracking(boolean enable)2043         void timeShiftEnablePositionTracking(boolean enable) {
2044             if (enable) {
2045                 mHandler.post(mTimeShiftPositionTrackingRunnable);
2046             } else {
2047                 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
2048                 mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
2049                 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
2050             }
2051         }
2052 
2053         /**
2054          * Schedules a task which checks whether the overlay view is detached and kills the process
2055          * if it is not. Note that this method is expected to be called in a non-main thread.
2056          */
scheduleOverlayViewCleanup()2057         void scheduleOverlayViewCleanup() {
2058             View overlayViewParent = mOverlayViewContainer;
2059             if (overlayViewParent != null) {
2060                 mOverlayViewCleanUpTask = new OverlayViewCleanUpTask();
2061                 mOverlayViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
2062                         overlayViewParent);
2063             }
2064         }
2065 
requestBroadcastInfo(BroadcastInfoRequest request)2066         void requestBroadcastInfo(BroadcastInfoRequest request) {
2067             onRequestBroadcastInfo(request);
2068         }
2069 
removeBroadcastInfo(int requestId)2070         void removeBroadcastInfo(int requestId) {
2071             onRemoveBroadcastInfo(requestId);
2072         }
2073 
requestAd(AdRequest request)2074         void requestAd(AdRequest request) {
2075             onRequestAd(request);
2076         }
2077 
notifyAdBufferReady(AdBuffer buffer)2078         void notifyAdBufferReady(AdBuffer buffer) {
2079             onAdBufferReady(buffer);
2080         }
2081 
onTvMessageReceived(int type, Bundle data)2082         void onTvMessageReceived(int type, Bundle data) {
2083             onTvMessage(type, data);
2084         }
2085 
2086         /**
2087          * Takes care of dispatching incoming input events and tells whether the event was handled.
2088          */
dispatchInputEvent(InputEvent event, InputEventReceiver receiver)2089         int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
2090             if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
2091             boolean isNavigationKey = false;
2092             boolean skipDispatchToOverlayView = false;
2093             if (event instanceof KeyEvent) {
2094                 KeyEvent keyEvent = (KeyEvent) event;
2095                 if (keyEvent.dispatch(this, mDispatcherState, this)) {
2096                     return TvInputManager.Session.DISPATCH_HANDLED;
2097                 }
2098                 isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
2099                 // When media keys and KEYCODE_MEDIA_AUDIO_TRACK are dispatched to ViewRootImpl,
2100                 // ViewRootImpl always consumes the keys. In this case, the application loses
2101                 // a chance to handle media keys. Therefore, media keys are not dispatched to
2102                 // ViewRootImpl.
2103                 skipDispatchToOverlayView = KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())
2104                         || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK;
2105             } else if (event instanceof MotionEvent) {
2106                 MotionEvent motionEvent = (MotionEvent) event;
2107                 final int source = motionEvent.getSource();
2108                 if (motionEvent.isTouchEvent()) {
2109                     if (onTouchEvent(motionEvent)) {
2110                         return TvInputManager.Session.DISPATCH_HANDLED;
2111                     }
2112                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
2113                     if (onTrackballEvent(motionEvent)) {
2114                         return TvInputManager.Session.DISPATCH_HANDLED;
2115                     }
2116                 } else {
2117                     if (onGenericMotionEvent(motionEvent)) {
2118                         return TvInputManager.Session.DISPATCH_HANDLED;
2119                     }
2120                 }
2121             }
2122             if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow()
2123                     || skipDispatchToOverlayView) {
2124                 return TvInputManager.Session.DISPATCH_NOT_HANDLED;
2125             }
2126             if (!mOverlayViewContainer.hasWindowFocus()) {
2127                 ViewRootImpl viewRoot = mOverlayViewContainer.getViewRootImpl();
2128                 viewRoot.windowFocusChanged(true);
2129             }
2130             if (isNavigationKey && mOverlayViewContainer.hasFocusable()) {
2131                 // If mOverlayView has focusable views, navigation key events should be always
2132                 // handled. If not, it can make the application UI navigation messed up.
2133                 // For example, in the case that the left-most view is focused, a left key event
2134                 // will not be handled in ViewRootImpl. Then, the left key event will be handled in
2135                 // the application during the UI navigation of the TV input.
2136                 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event);
2137                 return TvInputManager.Session.DISPATCH_HANDLED;
2138             } else {
2139                 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event, receiver);
2140                 return TvInputManager.Session.DISPATCH_IN_PROGRESS;
2141             }
2142         }
2143 
initialize(ITvInputSessionCallback callback)2144         private void initialize(ITvInputSessionCallback callback) {
2145             synchronized(mLock) {
2146                 mSessionCallback = callback;
2147                 for (Runnable runnable : mPendingActions) {
2148                     runnable.run();
2149                 }
2150                 mPendingActions.clear();
2151             }
2152         }
2153 
executeOrPostRunnableOnMainThread(Runnable action)2154         private void executeOrPostRunnableOnMainThread(Runnable action) {
2155             synchronized(mLock) {
2156                 if (mSessionCallback == null) {
2157                     // The session is not initialized yet.
2158                     mPendingActions.add(action);
2159                 } else {
2160                     if (mHandler.getLooper().isCurrentThread()) {
2161                         action.run();
2162                     } else {
2163                         // Posts the runnable if this is not called from the main thread
2164                         mHandler.post(action);
2165                     }
2166                 }
2167             }
2168         }
2169 
2170         private final class TimeShiftPositionTrackingRunnable implements Runnable {
2171             @Override
run()2172             public void run() {
2173                 long startPositionMs = onTimeShiftGetStartPosition();
2174                 if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME
2175                         || mStartPositionMs != startPositionMs) {
2176                     mStartPositionMs = startPositionMs;
2177                     notifyTimeShiftStartPositionChanged(startPositionMs);
2178                 }
2179                 long currentPositionMs = onTimeShiftGetCurrentPosition();
2180                 if (currentPositionMs < mStartPositionMs) {
2181                     Log.w(TAG, "Current position (" + currentPositionMs + ") cannot be earlier than"
2182                             + " start position (" + mStartPositionMs + "). Reset to the start "
2183                             + "position.");
2184                     currentPositionMs = mStartPositionMs;
2185                 }
2186                 if (mCurrentPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME
2187                         || mCurrentPositionMs != currentPositionMs) {
2188                     mCurrentPositionMs = currentPositionMs;
2189                     notifyTimeShiftCurrentPositionChanged(currentPositionMs);
2190                 }
2191                 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
2192                 mHandler.postDelayed(mTimeShiftPositionTrackingRunnable,
2193                         POSITION_UPDATE_INTERVAL_MS);
2194             }
2195         }
2196     }
2197 
2198     private static final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> {
2199         @Override
doInBackground(View... views)2200         protected Void doInBackground(View... views) {
2201             View overlayViewParent = views[0];
2202             try {
2203                 Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT_MS);
2204             } catch (InterruptedException e) {
2205                 return null;
2206             }
2207             if (isCancelled()) {
2208                 return null;
2209             }
2210             if (overlayViewParent.isAttachedToWindow()) {
2211                 Log.e(TAG, "Time out on releasing overlay view. Killing "
2212                         + overlayViewParent.getContext().getPackageName());
2213                 Process.killProcess(Process.myPid());
2214             }
2215             return null;
2216         }
2217     }
2218 
2219     /**
2220      * Base class for derived classes to implement to provide a TV input recording session.
2221      */
2222     public abstract static class RecordingSession {
2223         final Handler mHandler;
2224 
2225         private final Object mLock = new Object();
2226         // @GuardedBy("mLock")
2227         private ITvInputSessionCallback mSessionCallback;
2228         // @GuardedBy("mLock")
2229         private final List<Runnable> mPendingActions = new ArrayList<>();
2230 
2231         /**
2232          * Creates a new RecordingSession.
2233          *
2234          * @param context The context of the application
2235          */
RecordingSession(Context context)2236         public RecordingSession(Context context) {
2237             mHandler = new Handler(context.getMainLooper());
2238         }
2239 
2240         /**
2241          * Informs the application that this recording session has been tuned to the given channel
2242          * and is ready to start recording.
2243          *
2244          * <p>Upon receiving a call to {@link #onTune(Uri)}, the session is expected to tune to the
2245          * passed channel and call this method to indicate that it is now available for immediate
2246          * recording. When {@link #onStartRecording(Uri)} is called, recording must start with
2247          * minimal delay.
2248          *
2249          * @param channelUri The URI of a channel.
2250          */
notifyTuned(Uri channelUri)2251         public void notifyTuned(Uri channelUri) {
2252             executeOrPostRunnableOnMainThread(new Runnable() {
2253                 @MainThread
2254                 @Override
2255                 public void run() {
2256                     try {
2257                         if (DEBUG) Log.d(TAG, "notifyTuned");
2258                         if (mSessionCallback != null) {
2259                             mSessionCallback.onTuned(channelUri);
2260                         }
2261                     } catch (RemoteException e) {
2262                         Log.w(TAG, "error in notifyTuned", e);
2263                     }
2264                 }
2265             });
2266         }
2267 
2268         /**
2269          * Informs the application that this recording session has stopped recording and created a
2270          * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly
2271          * recorded program.
2272          *
2273          * <p>The recording session must call this method in response to {@link #onStopRecording()}.
2274          * The session may call it even before receiving a call to {@link #onStopRecording()} if a
2275          * partially recorded program is available when there is an error.
2276          *
2277          * @param recordedProgramUri The URI of the newly recorded program.
2278          */
notifyRecordingStopped(final Uri recordedProgramUri)2279         public void notifyRecordingStopped(final Uri recordedProgramUri) {
2280             executeOrPostRunnableOnMainThread(new Runnable() {
2281                 @MainThread
2282                 @Override
2283                 public void run() {
2284                     try {
2285                         if (DEBUG) Log.d(TAG, "notifyRecordingStopped");
2286                         if (mSessionCallback != null) {
2287                             mSessionCallback.onRecordingStopped(recordedProgramUri);
2288                         }
2289                     } catch (RemoteException e) {
2290                         Log.w(TAG, "error in notifyRecordingStopped", e);
2291                     }
2292                 }
2293             });
2294         }
2295 
2296         /**
2297          * Informs the application that there is an error and this recording session is no longer
2298          * able to start or continue recording. It may be called at any time after the recording
2299          * session is created until {@link #onRelease()} is called.
2300          *
2301          * <p>The application may release the current session upon receiving the error code through
2302          * {@link TvRecordingClient.RecordingCallback#onError(int)}. The session may call
2303          * {@link #notifyRecordingStopped(Uri)} if a partially recorded but still playable program
2304          * is available, before calling this method.
2305          *
2306          * @param error The error code. Should be one of the followings.
2307          * <ul>
2308          * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN}
2309          * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE}
2310          * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY}
2311          * </ul>
2312          */
notifyError(@vInputManager.RecordingError int error)2313         public void notifyError(@TvInputManager.RecordingError int error) {
2314             if (error < TvInputManager.RECORDING_ERROR_START
2315                     || error > TvInputManager.RECORDING_ERROR_END) {
2316                 Log.w(TAG, "notifyError - invalid error code (" + error
2317                         + ") is changed to RECORDING_ERROR_UNKNOWN.");
2318                 error = TvInputManager.RECORDING_ERROR_UNKNOWN;
2319             }
2320             final int validError = error;
2321             executeOrPostRunnableOnMainThread(new Runnable() {
2322                 @MainThread
2323                 @Override
2324                 public void run() {
2325                     try {
2326                         if (DEBUG) Log.d(TAG, "notifyError");
2327                         if (mSessionCallback != null) {
2328                             mSessionCallback.onError(validError);
2329                         }
2330                     } catch (RemoteException e) {
2331                         Log.w(TAG, "error in notifyError", e);
2332                     }
2333                 }
2334             });
2335         }
2336 
2337         /**
2338          * Dispatches an event to the application using this recording session.
2339          *
2340          * @param eventType The type of the event.
2341          * @param eventArgs Optional arguments of the event.
2342          * @hide
2343          */
2344         @SystemApi
notifySessionEvent(@onNull final String eventType, final Bundle eventArgs)2345         public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) {
2346             Preconditions.checkNotNull(eventType);
2347             executeOrPostRunnableOnMainThread(new Runnable() {
2348                 @MainThread
2349                 @Override
2350                 public void run() {
2351                     try {
2352                         if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
2353                         if (mSessionCallback != null) {
2354                             mSessionCallback.onSessionEvent(eventType, eventArgs);
2355                         }
2356                     } catch (RemoteException e) {
2357                         Log.w(TAG, "error in sending event (event=" + eventType + ")", e);
2358                     }
2359                 }
2360             });
2361         }
2362 
2363         /**
2364          * Called when the application requests to tune to a given channel for TV program recording.
2365          *
2366          * <p>The application may call this method before starting or after stopping recording, but
2367          * not during recording.
2368          *
2369          * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or
2370          * {@link #notifyError(int)} otherwise.
2371          *
2372          * @param channelUri The URI of a channel.
2373          */
onTune(Uri channelUri)2374         public abstract void onTune(Uri channelUri);
2375 
2376         /**
2377          * Called when the application requests to tune to a given channel for TV program recording.
2378          * Override this method in order to handle domain-specific features that are only known
2379          * between certain TV inputs and their clients.
2380          *
2381          * <p>The application may call this method before starting or after stopping recording, but
2382          * not during recording. The default implementation calls {@link #onTune(Uri)}.
2383          *
2384          * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or
2385          * {@link #notifyError(int)} otherwise.
2386          *
2387          * @param channelUri The URI of a channel.
2388          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
2389          *            name, i.e. prefixed with a package name you own, so that different developers
2390          *            will not create conflicting keys.
2391          */
onTune(Uri channelUri, Bundle params)2392         public void onTune(Uri channelUri, Bundle params) {
2393             onTune(channelUri);
2394         }
2395 
2396         /**
2397          * Called when the application requests to start TV program recording. Recording must start
2398          * immediately when this method is called.
2399          *
2400          * <p>The application may supply the URI for a TV program for filling in program specific
2401          * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
2402          * A non-null {@code programUri} implies the started recording should be of that specific
2403          * program, whereas null {@code programUri} does not impose such a requirement and the
2404          * recording can span across multiple TV programs. In either case, the application must call
2405          * {@link TvRecordingClient#stopRecording()} to stop the recording.
2406          *
2407          * <p>The session must call {@link #notifyError(int)} if the start request cannot be
2408          * fulfilled.
2409          *
2410          * @param programUri The URI for the TV program to record, built by
2411          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
2412          */
onStartRecording(@ullable Uri programUri)2413         public abstract void onStartRecording(@Nullable Uri programUri);
2414 
2415         /**
2416          * Called when the application requests to start TV program recording. Recording must start
2417          * immediately when this method is called.
2418          *
2419          * <p>The application may supply the URI for a TV program for filling in program specific
2420          * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
2421          * A non-null {@code programUri} implies the started recording should be of that specific
2422          * program, whereas null {@code programUri} does not impose such a requirement and the
2423          * recording can span across multiple TV programs. In either case, the application must call
2424          * {@link TvRecordingClient#stopRecording()} to stop the recording.
2425          *
2426          * <p>The session must call {@link #notifyError(int)} if the start request cannot be
2427          * fulfilled.
2428          *
2429          * @param programUri The URI for the TV program to record, built by
2430          *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
2431          * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped
2432          *            name, i.e. prefixed with a package name you own, so that different developers
2433          *            will not create conflicting keys.
2434          */
onStartRecording(@ullable Uri programUri, @NonNull Bundle params)2435         public void onStartRecording(@Nullable Uri programUri, @NonNull Bundle params) {
2436             onStartRecording(programUri);
2437         }
2438 
2439         /**
2440          * Called when the application requests to stop TV program recording. Recording must stop
2441          * immediately when this method is called.
2442          *
2443          * <p>The session must create a new data entry in the
2444          * {@link android.media.tv.TvContract.RecordedPrograms} table that describes the newly
2445          * recorded program and call {@link #notifyRecordingStopped(Uri)} with the URI to that
2446          * entry.
2447          * If the stop request cannot be fulfilled, the session must call {@link #notifyError(int)}.
2448          *
2449          */
onStopRecording()2450         public abstract void onStopRecording();
2451 
2452 
2453         /**
2454          * Called when the application requests to pause TV program recording. Recording must pause
2455          * immediately when this method is called.
2456          *
2457          * If the pause request cannot be fulfilled, the session must call
2458          * {@link #notifyError(int)}.
2459          *
2460          * @param params Domain-specific data for recording request.
2461          */
onPauseRecording(@onNull Bundle params)2462         public void onPauseRecording(@NonNull Bundle params) { }
2463 
2464         /**
2465          * Called when the application requests to resume TV program recording. Recording must
2466          * resume immediately when this method is called.
2467          *
2468          * If the resume request cannot be fulfilled, the session must call
2469          * {@link #notifyError(int)}.
2470          *
2471          * @param params Domain-specific data for recording request.
2472          */
onResumeRecording(@onNull Bundle params)2473         public void onResumeRecording(@NonNull Bundle params) { }
2474 
2475         /**
2476          * Called when the application requests to release all the resources held by this recording
2477          * session.
2478          */
onRelease()2479         public abstract void onRelease();
2480 
2481         /**
2482          * Processes a private command sent from the application to the TV input. This can be used
2483          * to provide domain-specific features that are only known between certain TV inputs and
2484          * their clients.
2485          *
2486          * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
2487          *            i.e. prefixed with a package name you own, so that different developers will
2488          *            not create conflicting commands.
2489          * @param data Any data to include with the command.
2490          */
onAppPrivateCommand(@onNull String action, Bundle data)2491         public void onAppPrivateCommand(@NonNull String action, Bundle data) {
2492         }
2493 
2494         /**
2495          * Calls {@link #onTune(Uri, Bundle)}.
2496          *
2497          */
tune(Uri channelUri, Bundle params)2498         void tune(Uri channelUri, Bundle params) {
2499             onTune(channelUri, params);
2500         }
2501 
2502         /**
2503          * Calls {@link #onRelease()}.
2504          *
2505          */
release()2506         void release() {
2507             onRelease();
2508         }
2509 
2510         /**
2511          * Calls {@link #onStartRecording(Uri, Bundle)}.
2512          *
2513          */
startRecording(@ullable Uri programUri, @NonNull Bundle params)2514         void startRecording(@Nullable  Uri programUri, @NonNull Bundle params) {
2515             onStartRecording(programUri, params);
2516         }
2517 
2518         /**
2519          * Calls {@link #onStopRecording()}.
2520          *
2521          */
stopRecording()2522         void stopRecording() {
2523             onStopRecording();
2524         }
2525 
2526         /**
2527          * Calls {@link #onPauseRecording(Bundle)}.
2528          *
2529          */
pauseRecording(@onNull Bundle params)2530         void pauseRecording(@NonNull Bundle params) {
2531             onPauseRecording(params);
2532         }
2533 
2534         /**
2535          * Calls {@link #onResumeRecording(Bundle)}.
2536          *
2537          */
resumeRecording(@onNull Bundle params)2538         void resumeRecording(@NonNull Bundle params) {
2539             onResumeRecording(params);
2540         }
2541 
2542         /**
2543          * Calls {@link #onAppPrivateCommand(String, Bundle)}.
2544          */
appPrivateCommand(String action, Bundle data)2545         void appPrivateCommand(String action, Bundle data) {
2546             onAppPrivateCommand(action, data);
2547         }
2548 
initialize(ITvInputSessionCallback callback)2549         private void initialize(ITvInputSessionCallback callback) {
2550             synchronized(mLock) {
2551                 mSessionCallback = callback;
2552                 for (Runnable runnable : mPendingActions) {
2553                     runnable.run();
2554                 }
2555                 mPendingActions.clear();
2556             }
2557         }
2558 
executeOrPostRunnableOnMainThread(Runnable action)2559         private void executeOrPostRunnableOnMainThread(Runnable action) {
2560             synchronized(mLock) {
2561                 if (mSessionCallback == null) {
2562                     // The session is not initialized yet.
2563                     mPendingActions.add(action);
2564                 } else {
2565                     if (mHandler.getLooper().isCurrentThread()) {
2566                         action.run();
2567                     } else {
2568                         // Posts the runnable if this is not called from the main thread
2569                         mHandler.post(action);
2570                     }
2571                 }
2572             }
2573         }
2574     }
2575 
2576     /**
2577      * Base class for a TV input session which represents an external device connected to a
2578      * hardware TV input.
2579      *
2580      * <p>This class is for an input which provides channels for the external set-top box to the
2581      * application. Once a TV input returns an implementation of this class on
2582      * {@link #onCreateSession(String)}, the framework will create a separate session for
2583      * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
2584      * that the user can see the screen of the hardware TV Input when she tunes to a channel from
2585      * this TV input. The implementation of this class is expected to change the channel of the
2586      * external set-top box via a proprietary protocol when {@link HardwareSession#onTune} is
2587      * requested by the application.
2588      *
2589      * <p>Note that this class is not for inputs for internal hardware like built-in tuner and HDMI
2590      * 1.
2591      *
2592      * @see #onCreateSession(String)
2593      */
2594     public abstract static class HardwareSession extends Session {
2595 
2596         /**
2597          * Creates a new HardwareSession.
2598          *
2599          * @param context The context of the application
2600          */
HardwareSession(Context context)2601         public HardwareSession(Context context) {
2602             super(context);
2603         }
2604 
2605         private TvInputManager.Session mHardwareSession;
2606         private ITvInputSession mProxySession;
2607         private ITvInputSessionCallback mProxySessionCallback;
2608         private Handler mServiceHandler;
2609 
2610         /**
2611          * Returns the hardware TV input ID the external device is connected to.
2612          *
2613          * <p>TV input is expected to provide {@link android.R.attr#setupActivity} so that
2614          * the application can launch it before using this TV input. The setup activity may let
2615          * the user select the hardware TV input to which the external device is connected. The ID
2616          * of the selected one should be stored in the TV input so that it can be returned here.
2617          */
getHardwareInputId()2618         public abstract String getHardwareInputId();
2619 
2620         private final TvInputManager.SessionCallback mHardwareSessionCallback =
2621                 new TvInputManager.SessionCallback() {
2622                     @Override
2623                     public void onSessionCreated(TvInputManager.Session session) {
2624                         mHardwareSession = session;
2625                         SomeArgs args = SomeArgs.obtain();
2626                         if (session != null) {
2627                             args.arg1 = HardwareSession.this;
2628                             args.arg2 = mProxySession;
2629                             args.arg3 = mProxySessionCallback;
2630                             args.arg4 = session.getToken();
2631                             session.tune(TvContract.buildChannelUriForPassthroughInput(
2632                                     getHardwareInputId()));
2633                         } else {
2634                             args.arg1 = null;
2635                             args.arg2 = null;
2636                             args.arg3 = mProxySessionCallback;
2637                             args.arg4 = null;
2638                             onRelease();
2639                         }
2640                         mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
2641                                         args).sendToTarget();
2642                     }
2643 
2644                     @Override
2645                     public void onVideoAvailable(final TvInputManager.Session session) {
2646                         if (mHardwareSession == session) {
2647                             onHardwareVideoAvailable();
2648                         }
2649                     }
2650 
2651                     @Override
2652                     public void onVideoUnavailable(final TvInputManager.Session session,
2653                             final int reason) {
2654                         if (mHardwareSession == session) {
2655                             onHardwareVideoUnavailable(reason);
2656                         }
2657                     }
2658                 };
2659 
2660         /**
2661          * This method will not be called in {@link HardwareSession}. Framework will
2662          * forward the application's surface to the hardware TV input.
2663          */
2664         @Override
onSetSurface(Surface surface)2665         public final boolean onSetSurface(Surface surface) {
2666             Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
2667             return false;
2668         }
2669 
2670         /**
2671          * Called when the underlying hardware TV input session calls
2672          * {@link TvInputService.Session#notifyVideoAvailable()}.
2673          */
onHardwareVideoAvailable()2674         public void onHardwareVideoAvailable() { }
2675 
2676         /**
2677          * Called when the underlying hardware TV input session calls
2678          * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
2679          *
2680          * @param reason The reason that the hardware TV input stopped the playback:
2681          * <ul>
2682          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
2683          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
2684          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
2685          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
2686          * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
2687          * </ul>
2688          */
onHardwareVideoUnavailable(int reason)2689         public void onHardwareVideoUnavailable(int reason) { }
2690 
2691         @Override
release()2692         void release() {
2693             if (mHardwareSession != null) {
2694                 mHardwareSession.release();
2695                 mHardwareSession = null;
2696             }
2697             super.release();
2698         }
2699     }
2700 
2701     /** @hide */
isNavigationKey(int keyCode)2702     public static boolean isNavigationKey(int keyCode) {
2703         switch (keyCode) {
2704             case KeyEvent.KEYCODE_DPAD_LEFT:
2705             case KeyEvent.KEYCODE_DPAD_RIGHT:
2706             case KeyEvent.KEYCODE_DPAD_UP:
2707             case KeyEvent.KEYCODE_DPAD_DOWN:
2708             case KeyEvent.KEYCODE_DPAD_CENTER:
2709             case KeyEvent.KEYCODE_PAGE_UP:
2710             case KeyEvent.KEYCODE_PAGE_DOWN:
2711             case KeyEvent.KEYCODE_MOVE_HOME:
2712             case KeyEvent.KEYCODE_MOVE_END:
2713             case KeyEvent.KEYCODE_TAB:
2714             case KeyEvent.KEYCODE_SPACE:
2715             case KeyEvent.KEYCODE_ENTER:
2716                 return true;
2717         }
2718         return false;
2719     }
2720 
2721     @SuppressLint("HandlerLeak")
2722     private final class ServiceHandler extends Handler {
2723         private static final int DO_CREATE_SESSION = 1;
2724         private static final int DO_NOTIFY_SESSION_CREATED = 2;
2725         private static final int DO_CREATE_RECORDING_SESSION = 3;
2726         private static final int DO_ADD_HARDWARE_INPUT = 4;
2727         private static final int DO_REMOVE_HARDWARE_INPUT = 5;
2728         private static final int DO_ADD_HDMI_INPUT = 6;
2729         private static final int DO_REMOVE_HDMI_INPUT = 7;
2730         private static final int DO_UPDATE_HDMI_INPUT = 8;
2731 
broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo)2732         private void broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo) {
2733             int n = mCallbacks.beginBroadcast();
2734             for (int i = 0; i < n; ++i) {
2735                 try {
2736                     mCallbacks.getBroadcastItem(i).addHardwareInput(deviceId, inputInfo);
2737                 } catch (RemoteException e) {
2738                     Log.e(TAG, "error in broadcastAddHardwareInput", e);
2739                 }
2740             }
2741             mCallbacks.finishBroadcast();
2742         }
2743 
broadcastAddHdmiInput(int id, TvInputInfo inputInfo)2744         private void broadcastAddHdmiInput(int id, TvInputInfo inputInfo) {
2745             int n = mCallbacks.beginBroadcast();
2746             for (int i = 0; i < n; ++i) {
2747                 try {
2748                     mCallbacks.getBroadcastItem(i).addHdmiInput(id, inputInfo);
2749                 } catch (RemoteException e) {
2750                     Log.e(TAG, "error in broadcastAddHdmiInput", e);
2751                 }
2752             }
2753             mCallbacks.finishBroadcast();
2754         }
2755 
broadcastRemoveHardwareInput(String inputId)2756         private void broadcastRemoveHardwareInput(String inputId) {
2757             int n = mCallbacks.beginBroadcast();
2758             for (int i = 0; i < n; ++i) {
2759                 try {
2760                     mCallbacks.getBroadcastItem(i).removeHardwareInput(inputId);
2761                 } catch (RemoteException e) {
2762                     Log.e(TAG, "error in broadcastRemoveHardwareInput", e);
2763                 }
2764             }
2765             mCallbacks.finishBroadcast();
2766         }
2767 
2768         @Override
handleMessage(Message msg)2769         public final void handleMessage(Message msg) {
2770             switch (msg.what) {
2771                 case DO_CREATE_SESSION: {
2772                     SomeArgs args = (SomeArgs) msg.obj;
2773                     InputChannel channel = (InputChannel) args.arg1;
2774                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
2775                     String inputId = (String) args.arg3;
2776                     String sessionId = (String) args.arg4;
2777                     AttributionSource tvAppAttributionSource = (AttributionSource) args.arg5;
2778                     args.recycle();
2779                     Session sessionImpl =
2780                             onCreateSession(inputId, sessionId, tvAppAttributionSource);
2781                     if (sessionImpl == null) {
2782                         try {
2783                             // Failed to create a session.
2784                             cb.onSessionCreated(null, null);
2785                         } catch (RemoteException e) {
2786                             Log.e(TAG, "error in onSessionCreated", e);
2787                         }
2788                         return;
2789                     }
2790                     ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
2791                             sessionImpl, channel);
2792                     if (sessionImpl instanceof HardwareSession) {
2793                         HardwareSession proxySession =
2794                                 ((HardwareSession) sessionImpl);
2795                         String hardwareInputId = proxySession.getHardwareInputId();
2796                         if (TextUtils.isEmpty(hardwareInputId) ||
2797                                 !isPassthroughInput(hardwareInputId)) {
2798                             if (TextUtils.isEmpty(hardwareInputId)) {
2799                                 Log.w(TAG, "Hardware input id is not setup yet.");
2800                             } else {
2801                                 Log.w(TAG, "Invalid hardware input id : " + hardwareInputId);
2802                             }
2803                             sessionImpl.onRelease();
2804                             try {
2805                                 cb.onSessionCreated(null, null);
2806                             } catch (RemoteException e) {
2807                                 Log.e(TAG, "error in onSessionCreated", e);
2808                             }
2809                             return;
2810                         }
2811                         proxySession.mProxySession = stub;
2812                         proxySession.mProxySessionCallback = cb;
2813                         proxySession.mServiceHandler = mServiceHandler;
2814                         TvInputManager manager = (TvInputManager) getSystemService(
2815                                 Context.TV_INPUT_SERVICE);
2816                         manager.createSession(hardwareInputId, tvAppAttributionSource,
2817                                 proxySession.mHardwareSessionCallback, mServiceHandler);
2818                     } else {
2819                         SomeArgs someArgs = SomeArgs.obtain();
2820                         someArgs.arg1 = sessionImpl;
2821                         someArgs.arg2 = stub;
2822                         someArgs.arg3 = cb;
2823                         someArgs.arg4 = null;
2824                         mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
2825                                 someArgs).sendToTarget();
2826                     }
2827                     return;
2828                 }
2829                 case DO_NOTIFY_SESSION_CREATED: {
2830                     SomeArgs args = (SomeArgs) msg.obj;
2831                     Session sessionImpl = (Session) args.arg1;
2832                     ITvInputSession stub = (ITvInputSession) args.arg2;
2833                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg3;
2834                     IBinder hardwareSessionToken = (IBinder) args.arg4;
2835                     try {
2836                         cb.onSessionCreated(stub, hardwareSessionToken);
2837                     } catch (RemoteException e) {
2838                         Log.e(TAG, "error in onSessionCreated", e);
2839                     }
2840                     if (sessionImpl != null) {
2841                         sessionImpl.initialize(cb);
2842                     }
2843                     args.recycle();
2844                     return;
2845                 }
2846                 case DO_CREATE_RECORDING_SESSION: {
2847                     SomeArgs args = (SomeArgs) msg.obj;
2848                     ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1;
2849                     String inputId = (String) args.arg2;
2850                     String sessionId = (String) args.arg3;
2851                     args.recycle();
2852                     RecordingSession recordingSessionImpl =
2853                             onCreateRecordingSession(inputId, sessionId);
2854                     if (recordingSessionImpl == null) {
2855                         try {
2856                             // Failed to create a recording session.
2857                             cb.onSessionCreated(null, null);
2858                         } catch (RemoteException e) {
2859                             Log.e(TAG, "error in onSessionCreated", e);
2860                         }
2861                         return;
2862                     }
2863                     ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
2864                             recordingSessionImpl);
2865                     try {
2866                         cb.onSessionCreated(stub, null);
2867                     } catch (RemoteException e) {
2868                         Log.e(TAG, "error in onSessionCreated", e);
2869                     }
2870                     recordingSessionImpl.initialize(cb);
2871                     return;
2872                 }
2873                 case DO_ADD_HARDWARE_INPUT: {
2874                     TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
2875                     TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
2876                     if (inputInfo != null) {
2877                         broadcastAddHardwareInput(hardwareInfo.getDeviceId(), inputInfo);
2878                     }
2879                     return;
2880                 }
2881                 case DO_REMOVE_HARDWARE_INPUT: {
2882                     TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
2883                     String inputId = onHardwareRemoved(hardwareInfo);
2884                     if (inputId != null) {
2885                         broadcastRemoveHardwareInput(inputId);
2886                     }
2887                     return;
2888                 }
2889                 case DO_ADD_HDMI_INPUT: {
2890                     HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
2891                     TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
2892                     if (inputInfo != null) {
2893                         broadcastAddHdmiInput(deviceInfo.getId(), inputInfo);
2894                     }
2895                     return;
2896                 }
2897                 case DO_REMOVE_HDMI_INPUT: {
2898                     HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
2899                     String inputId = onHdmiDeviceRemoved(deviceInfo);
2900                     if (inputId != null) {
2901                         broadcastRemoveHardwareInput(inputId);
2902                     }
2903                     return;
2904                 }
2905                 case DO_UPDATE_HDMI_INPUT: {
2906                     HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
2907                     onHdmiDeviceUpdated(deviceInfo);
2908                     return;
2909                 }
2910                 default: {
2911                     Log.w(TAG, "Unhandled message code: " + msg.what);
2912                     return;
2913                 }
2914             }
2915         }
2916     }
2917 }
2918