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