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