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