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
17package android.media.tv;
18
19import android.annotation.FloatRange;
20import android.annotation.MainThread;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.SuppressLint;
24import android.annotation.SystemApi;
25import android.app.ActivityManager;
26import android.app.Service;
27import android.content.Context;
28import android.content.Intent;
29import android.graphics.PixelFormat;
30import android.graphics.Rect;
31import android.hardware.hdmi.HdmiDeviceInfo;
32import android.media.PlaybackParams;
33import android.net.Uri;
34import android.os.AsyncTask;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.Message;
39import android.os.Process;
40import android.os.RemoteCallbackList;
41import android.os.RemoteException;
42import android.text.TextUtils;
43import android.util.Log;
44import android.view.Gravity;
45import android.view.InputChannel;
46import android.view.InputDevice;
47import android.view.InputEvent;
48import android.view.InputEventReceiver;
49import android.view.KeyEvent;
50import android.view.MotionEvent;
51import android.view.Surface;
52import android.view.View;
53import android.view.WindowManager;
54import android.view.accessibility.CaptioningManager;
55import android.widget.FrameLayout;
56
57import com.android.internal.os.SomeArgs;
58import com.android.internal.util.Preconditions;
59
60import java.util.ArrayList;
61import java.util.HashSet;
62import java.util.List;
63import 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 */
77public 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
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