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