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