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