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