OSDN Git Service

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