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