TvInputService.java revision 783645e99f909ffc7a2d5d2fca9324cc0e9b7362
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.SuppressLint;
20import android.annotation.SystemApi;
21import android.app.Service;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.PixelFormat;
25import android.graphics.Rect;
26import android.hardware.hdmi.HdmiCecDeviceInfo;
27import android.net.Uri;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.RemoteCallbackList;
33import android.os.RemoteException;
34import android.util.Log;
35import android.view.Gravity;
36import android.view.InputChannel;
37import android.view.InputDevice;
38import android.view.InputEvent;
39import android.view.InputEventReceiver;
40import android.view.KeyEvent;
41import android.view.MotionEvent;
42import android.view.Surface;
43import android.view.View;
44import android.view.WindowManager;
45import android.view.accessibility.CaptioningManager;
46
47import com.android.internal.annotations.VisibleForTesting;
48import com.android.internal.os.SomeArgs;
49
50import java.util.List;
51
52/**
53 * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
54 * provides pass-through video or broadcast TV programs.
55 * <p>
56 * Applications will not normally use this service themselves, instead relying on the standard
57 * interaction provided by {@link TvView}. Those implementing TV input services should normally do
58 * so by deriving from this class and providing their own session implementation based on
59 * {@link TvInputService.Session}. All TV input services must require that clients hold the
60 * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this
61 * permission is not specified in the manifest, the system will refuse to bind to that TV input
62 * service.
63 * </p>
64 */
65public abstract class TvInputService extends Service {
66    // STOPSHIP: Turn debugging off.
67    private static final boolean DEBUG = true;
68    private static final String TAG = "TvInputService";
69
70    /**
71     * This is the interface name that a service implementing a TV input should say that it support
72     * -- that is, this is the action it uses for its intent filter. To be supported, the service
73     * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
74     * other applications cannot abuse it.
75     */
76    public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
77
78    /**
79     * Name under which a TvInputService component publishes information about itself.
80     * This meta-data must reference an XML resource containing an
81     * <code>&lt;{@link android.R.styleable#TvInputService tv-input}&gt;</code>
82     * tag.
83     */
84    public static final String SERVICE_META_DATA = "android.media.tv.input";
85
86    private final Handler mHandler = new ServiceHandler();
87    private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
88            new RemoteCallbackList<ITvInputServiceCallback>();
89
90    @Override
91    public final IBinder onBind(Intent intent) {
92        return new ITvInputService.Stub() {
93            @Override
94            public void registerCallback(ITvInputServiceCallback cb) {
95                if (cb != null) {
96                    mCallbacks.register(cb);
97                }
98            }
99
100            @Override
101            public void unregisterCallback(ITvInputServiceCallback cb) {
102                if (cb != null) {
103                    mCallbacks.unregister(cb);
104                }
105            }
106
107            @Override
108            public void createSession(InputChannel channel, ITvInputSessionCallback cb,
109                    String inputId) {
110                if (channel == null) {
111                    Log.w(TAG, "Creating session without input channel");
112                }
113                if (cb == null) {
114                    return;
115                }
116                SomeArgs args = SomeArgs.obtain();
117                args.arg1 = channel;
118                args.arg2 = cb;
119                args.arg3 = inputId;
120                mHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
121            }
122
123            @Override
124            public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
125                mHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT,
126                        hardwareInfo).sendToTarget();
127            }
128
129            @Override
130            public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
131                mHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_TV_INPUT,
132                        hardwareInfo).sendToTarget();
133            }
134
135            @Override
136            public void notifyHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
137                mHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_CEC_TV_INPUT,
138                        cecDeviceInfo).sendToTarget();
139            }
140
141            @Override
142            public void notifyHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
143                mHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_CEC_TV_INPUT,
144                        cecDeviceInfo).sendToTarget();
145            }
146        };
147    }
148
149    /**
150     * Get the number of callbacks that are registered.
151     * @hide
152     */
153    @VisibleForTesting
154    public final int getRegisteredCallbackCount() {
155        return mCallbacks.getRegisteredCallbackCount();
156    }
157
158    /**
159     * Returns a concrete implementation of {@link Session}.
160     * <p>
161     * May return {@code null} if this TV input service fails to create a session for some reason.
162     * </p>
163     * @param inputId The ID of the TV input associated with the session.
164     */
165    public abstract Session onCreateSession(String inputId);
166
167    /**
168     * Returns a new {@link TvInputInfo} object if this service is responsible for
169     * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
170     * ignoring all hardware input.
171     *
172     * @param hardwareInfo {@link TvInputHardwareInfo} object just added.
173     * @hide
174     */
175    @SystemApi
176    public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
177        return null;
178    }
179
180    /**
181     * Returns the input ID for {@code deviceId} if it is handled by this service;
182     * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware
183     * input.
184     *
185     * @param hardwareInfo {@link TvInputHardwareInfo} object just removed.
186     * @hide
187     */
188    @SystemApi
189    public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
190        return null;
191    }
192
193    /**
194     * Returns a new {@link TvInputInfo} object if this service is responsible for
195     * {@code cecDeviceInfo}; otherwise, return {@code null}. Override to modify default behavior
196     * of ignoring all HDMI CEC logical input device.
197     *
198     * @param cecDeviceInfo {@link HdmiCecDeviceInfo} object just added.
199     * @hide
200     */
201    @SystemApi
202    public TvInputInfo onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
203        return null;
204    }
205
206    /**
207     * Returns the input ID for {@code logicalAddress} if it is handled by this service;
208     * otherwise, return {@code null}. Override to modify default behavior of ignoring all HDMI CEC
209     * logical input device.
210     *
211     * @param cecDeviceInfo {@link HdmiCecDeviceInfo} object just removed.
212     * @hide
213     */
214    @SystemApi
215    public String onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
216        return null;
217    }
218
219    /**
220     * Notify wrapped TV input ID of current input to TV input framework manager
221     *
222     * @param inputId The TV input ID of {@link TvInputPassthroughWrapperService}
223     * @param wrappedInputId The ID of the wrapped TV input such as external pass-though TV input
224     * @hide
225     */
226    public final void notifyWrappedInputId(String inputId, String wrappedInputId) {
227        SomeArgs args = SomeArgs.obtain();
228        args.arg1 = inputId;
229        args.arg2 = wrappedInputId;
230        mHandler.obtainMessage(TvInputService.ServiceHandler.DO_SET_WRAPPED_TV_INPUT_ID,
231                args).sendToTarget();
232    }
233
234    /**
235     * Base class for derived classes to implement to provide a TV input session.
236     */
237    public abstract class Session implements KeyEvent.Callback {
238        private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
239        private final WindowManager mWindowManager;
240        private WindowManager.LayoutParams mWindowParams;
241        private Surface mSurface;
242        private View mOverlayView;
243        private boolean mOverlayViewEnabled;
244        private IBinder mWindowToken;
245        private Rect mOverlayFrame;
246        private ITvInputSessionCallback mSessionCallback;
247
248        public Session() {
249            mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
250        }
251
252        /**
253         * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
254         * called explicitly after the session is created to enable the overlay view.
255         *
256         * @param enable {@code true} if you want to enable the overlay view. {@code false}
257         *            otherwise.
258         */
259        public void setOverlayViewEnabled(final boolean enable) {
260            mHandler.post(new Runnable() {
261                @Override
262                public void run() {
263                    if (enable == mOverlayViewEnabled) {
264                        return;
265                    }
266                    mOverlayViewEnabled = enable;
267                    if (enable) {
268                        if (mWindowToken != null) {
269                            createOverlayView(mWindowToken, mOverlayFrame);
270                        }
271                    } else {
272                        removeOverlayView(false);
273                    }
274                }
275            });
276        }
277
278        /**
279         * Dispatches an event to the application using this session.
280         *
281         * @param eventType The type of the event.
282         * @param eventArgs Optional arguments of the event.
283         * @hide
284         */
285        public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
286            if (eventType == null) {
287                throw new IllegalArgumentException("eventType should not be null.");
288            }
289            mHandler.post(new Runnable() {
290                @Override
291                public void run() {
292                    try {
293                        if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
294                        mSessionCallback.onSessionEvent(eventType, eventArgs);
295                    } catch (RemoteException e) {
296                        Log.w(TAG, "error in sending event (event=" + eventType + ")");
297                    }
298                }
299            });
300        }
301
302        /**
303         * Notifies the channel of the session is retuned by TV input.
304         *
305         * @param channelUri The URI of a channel.
306         */
307        public void notifyChannelRetuned(final Uri channelUri) {
308            mHandler.post(new Runnable() {
309                @Override
310                public void run() {
311                    try {
312                        if (DEBUG) Log.d(TAG, "notifyChannelRetuned");
313                        mSessionCallback.onChannelRetuned(channelUri);
314                    } catch (RemoteException e) {
315                        Log.w(TAG, "error in notifyChannelRetuned");
316                    }
317                }
318            });
319        }
320
321        /**
322         * Sends the change on the track information. This is expected to be called whenever a
323         * track is added/removed and the metadata of a track is modified.
324         *
325         * @param tracks A list which includes track information.
326         */
327        public void notifyTrackInfoChanged(final List<TvTrackInfo> tracks) {
328            if (!TvTrackInfo.checkSanity(tracks)) {
329                throw new IllegalArgumentException(
330                        "Two or more selected tracks for a track type.");
331            }
332            mHandler.post(new Runnable() {
333                @Override
334                public void run() {
335                    try {
336                        if (DEBUG) Log.d(TAG, "notifyTrackInfoChanged");
337                        mSessionCallback.onTrackInfoChanged(tracks);
338                    } catch (RemoteException e) {
339                        Log.w(TAG, "error in notifyTrackInfoChanged");
340                    }
341                }
342            });
343        }
344
345        /**
346         * Informs the application that video is available and the playback of the TV stream has
347         * been started.
348         */
349        public void notifyVideoAvailable() {
350            mHandler.post(new Runnable() {
351                @Override
352                public void run() {
353                    try {
354                        if (DEBUG) Log.d(TAG, "notifyVideoAvailable");
355                        mSessionCallback.onVideoAvailable();
356                    } catch (RemoteException e) {
357                        Log.w(TAG, "error in notifyVideoAvailable");
358                    }
359                }
360            });
361        }
362
363        /**
364         * Informs the application that video is not available, so the TV input cannot continue
365         * playing the TV stream.
366         *
367         * @param reason The reason why the TV input stopped the playback:
368         * <ul>
369         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
370         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
371         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
372         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
373         * </ul>
374         */
375        public void notifyVideoUnavailable(final int reason) {
376            if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
377                    || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
378                throw new IllegalArgumentException("Unknown reason: " + reason);
379            }
380            mHandler.post(new Runnable() {
381                @Override
382                public void run() {
383                    try {
384                        if (DEBUG) Log.d(TAG, "notifyVideoUnavailable");
385                        mSessionCallback.onVideoUnavailable(reason);
386                    } catch (RemoteException e) {
387                        Log.w(TAG, "error in notifyVideoUnavailable");
388                    }
389                }
390            });
391        }
392
393        /**
394         * Informs the application that the user is allowed to watch the current program content.
395         * <p>
396         * Each TV input service is required to query the system whether the user is allowed to
397         * watch the current program before showing it to the user if the parental controls is
398         * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
399         * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
400         * service should block the content or not is determined by invoking
401         * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
402         * with the content rating for the current program. Then the {@link TvInputManager} makes a
403         * judgment based on the user blocked ratings stored in the secure settings and returns the
404         * result. If the rating in question turns out to be allowed by the user, the TV input
405         * service must call this method to notify the application that is permitted to show the
406         * content.
407         * </p><p>
408         * Each TV input service also needs to continuously listen to any changes made to the
409         * parental controls settings by registering a broadcast receiver to receive
410         * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
411         * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
412         * reevaluate the current program with the new parental controls settings.
413         * </p>
414         *
415         * @see #notifyContentBlocked
416         * @see TvInputManager
417         */
418        public void notifyContentAllowed() {
419            mHandler.post(new Runnable() {
420                @Override
421                public void run() {
422                    try {
423                        if (DEBUG) Log.d(TAG, "notifyContentAllowed");
424                        mSessionCallback.onContentAllowed();
425                    } catch (RemoteException e) {
426                        Log.w(TAG, "error in notifyContentAllowed");
427                    }
428                }
429            });
430        }
431
432        /**
433         * Informs the application that the current program content is blocked by parent controls.
434         * <p>
435         * Each TV input service is required to query the system whether the user is allowed to
436         * watch the current program before showing it to the user if the parental controls is
437         * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
438         * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
439         * service should block the content or not is determined by invoking
440         * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
441         * with the content rating for the current program. Then the {@link TvInputManager} makes a
442         * judgment based on the user blocked ratings stored in the secure settings and returns the
443         * result. If the rating in question turns out to be blocked, the TV input service must
444         * immediately block the content and call this method with the content rating of the current
445         * program to prompt the PIN verification screen.
446         * </p><p>
447         * Each TV input service also needs to continuously listen to any changes made to the
448         * parental controls settings by registering a broadcast receiver to receive
449         * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
450         * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
451         * reevaluate the current program with the new parental controls settings.
452         * </p>
453         *
454         * @param rating The content rating for the current TV program.
455         * @see #notifyContentAllowed
456         * @see TvInputManager
457         */
458        public void notifyContentBlocked(final TvContentRating rating) {
459            mHandler.post(new Runnable() {
460                @Override
461                public void run() {
462                    try {
463                        if (DEBUG) Log.d(TAG, "notifyContentBlocked");
464                        mSessionCallback.onContentBlocked(rating.flattenToString());
465                    } catch (RemoteException e) {
466                        Log.w(TAG, "error in notifyContentBlocked");
467                    }
468                }
469            });
470        }
471
472        /**
473         * Called when the session is released.
474         */
475        public abstract void onRelease();
476
477        /**
478         * Set the current session as the "main" session. See {@link TvView#setMainTvView} for the
479         * meaning of "main".
480         * <p>
481         * This is primarily for HDMI-CEC active source management. TV input service that manages
482         * HDMI-CEC logical device should make sure not only to select the corresponding HDMI
483         * logical device as source device on {@code onSetMainSession(true)}, but also to select
484         * internal device on {@code onSetMainSession(false)}. Also, if surface is set to non-main
485         * session, it needs to select internal device after temporarily selecting corresponding
486         * HDMI logical device for set up.
487         * </p><p>
488         * It is guaranteed that {@code onSetMainSession(true)} for new session is called first,
489         * and {@code onSetMainSession(false)} for old session is called afterwards. This allows
490         * {@code onSetMainSession(false)} to be no-op when TV input service knows that the next
491         * main session corresponds to another HDMI logical device. Practically, this implies that
492         * one TV input service should handle all HDMI port and HDMI-CEC logical devices for smooth
493         * active source transition.
494         * </p>
495         *
496         * @param isMainSession If true, session is main.
497         * @hide
498         */
499        @SystemApi
500        public void onSetMainSession(boolean isMainSession) {
501        }
502
503        /**
504         * Sets the {@link Surface} for the current input session on which the TV input renders
505         * video.
506         *
507         * @param surface {@link Surface} an application passes to this TV input session.
508         * @return {@code true} if the surface was set, {@code false} otherwise.
509         */
510        public abstract boolean onSetSurface(Surface surface);
511
512        /**
513         * Called after any structural changes (format or size) have been made to the
514         * {@link Surface} passed by {@link #onSetSurface}. This method is always called
515         * at least once, after {@link #onSetSurface} with non-null {@link Surface} is called.
516         *
517         * @param format The new PixelFormat of the {@link Surface}.
518         * @param width The new width of the {@link Surface}.
519         * @param height The new height of the {@link Surface}.
520         */
521        public void onSurfaceChanged(int format, int width, int height) {
522        }
523
524        /**
525         * Sets the relative stream volume of the current TV input session to handle the change of
526         * audio focus by setting.
527         *
528         * @param volume Volume scale from 0.0 to 1.0.
529         */
530        public abstract void onSetStreamVolume(float volume);
531
532        /**
533         * Tunes to a given channel. When the video is available, {@link #notifyVideoAvailable()}
534         * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the
535         * TV input cannot continue playing the given channel.
536         *
537         * @param channelUri The URI of the channel.
538         * @return {@code true} the tuning was successful, {@code false} otherwise.
539         */
540        public abstract boolean onTune(Uri channelUri);
541
542        /**
543         * Enables or disables the caption.
544         * <p>
545         * The locale for the user's preferred captioning language can be obtained by calling
546         * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
547         *
548         * @param enabled {@code true} to enable, {@code false} to disable.
549         * @see CaptioningManager
550         */
551        public abstract void onSetCaptionEnabled(boolean enabled);
552
553        /**
554         * Requests to unblock the content according to the given rating.
555         * <p>
556         * The implementation should unblock the content.
557         * TV input service has responsibility to decide when/how the unblock expires
558         * while it can keep previously unblocked ratings in order not to ask a user
559         * to unblock whenever a content rating is changed.
560         * Therefore an unblocked rating can be valid for a channel, a program,
561         * or certain amount of time depending on the implementation.
562         * </p>
563         *
564         * @param unblockedRating An unblocked content rating
565         */
566        public void onUnblockContent(TvContentRating unblockedRating) {
567        }
568
569        /**
570         * Selects a given track.
571         * <p>
572         * If it is called multiple times on the same type of track (ie. Video, Audio, Text), the
573         * track selected previously should be unselected in the implementation of this method.
574         * Also, if the select operation was successful, the implementation should call
575         * {@link #notifyTrackInfoChanged(List)} to report the updated track information.
576         * </p>
577         *
578         * @param track The track to be selected.
579         * @return {@code true} if the select operation was successful, {@code false} otherwise.
580         * @see #notifyTrackInfoChanged
581         * @see TvTrackInfo#KEY_IS_SELECTED
582         */
583        public boolean onSelectTrack(TvTrackInfo track) {
584            return false;
585        }
586
587        /**
588         * Unselects a given track.
589         * <p>
590         * If the unselect operation was successful, the implementation should call
591         * {@link #notifyTrackInfoChanged(List)} to report the updated track information.
592         * </p>
593         *
594         * @param track The track to be unselected.
595         * @return {@code true} if the unselect operation was successful, {@code false} otherwise.
596         * @see #notifyTrackInfoChanged
597         * @see TvTrackInfo#KEY_IS_SELECTED
598         */
599        public boolean onUnselectTrack(TvTrackInfo track) {
600            return false;
601        }
602
603        /**
604         * Processes a private command sent from the application to the TV input. This can be used
605         * to provide domain-specific features that are only known between certain TV inputs and
606         * their clients.
607         *
608         * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
609         *            i.e. prefixed with a package name you own, so that different developers will
610         *            not create conflicting commands.
611         * @param data Any data to include with the command.
612         * @hide
613         */
614        @SystemApi
615        public void onAppPrivateCommand(String action, Bundle data) {
616        }
617
618        /**
619         * Called when an application requests to create an overlay view. Each session
620         * implementation can override this method and return its own view.
621         *
622         * @return a view attached to the overlay window
623         */
624        public View onCreateOverlayView() {
625            return null;
626        }
627
628        /**
629         * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
630         * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
631         * <p>
632         * Override this to intercept key down events before they are processed by the application.
633         * If you return true, the application will not process the event itself. If you return
634         * false, the normal application processing will occur as if the TV input had not seen the
635         * event at all.
636         *
637         * @param keyCode The value in event.getKeyCode().
638         * @param event Description of the key event.
639         * @return If you handled the event, return {@code true}. If you want to allow the event to
640         *         be handled by the next receiver, return {@code false}.
641         */
642        @Override
643        public boolean onKeyDown(int keyCode, KeyEvent event) {
644            return false;
645        }
646
647        /**
648         * Default implementation of
649         * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
650         * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
651         * <p>
652         * Override this to intercept key long press events before they are processed by the
653         * application. If you return true, the application will not process the event itself. If
654         * you return false, the normal application processing will occur as if the TV input had not
655         * seen the event at all.
656         *
657         * @param keyCode The value in event.getKeyCode().
658         * @param event Description of the key event.
659         * @return If you handled the event, return {@code true}. If you want to allow the event to
660         *         be handled by the next receiver, return {@code false}.
661         */
662        @Override
663        public boolean onKeyLongPress(int keyCode, KeyEvent event) {
664            return false;
665        }
666
667        /**
668         * Default implementation of
669         * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
670         * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
671         * <p>
672         * Override this to intercept special key multiple events before they are processed by the
673         * application. If you return true, the application will not itself process the event. If
674         * you return false, the normal application processing will occur as if the TV input had not
675         * seen the event at all.
676         *
677         * @param keyCode The value in event.getKeyCode().
678         * @param count The number of times the action was made.
679         * @param event Description of the key event.
680         * @return If you handled the event, return {@code true}. If you want to allow the event to
681         *         be handled by the next receiver, return {@code false}.
682         */
683        @Override
684        public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
685            return false;
686        }
687
688        /**
689         * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
690         * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
691         * <p>
692         * Override this to intercept key up events before they are processed by the application. If
693         * you return true, the application will not itself process the event. If you return false,
694         * the normal application processing will occur as if the TV input had not seen the event at
695         * all.
696         *
697         * @param keyCode The value in event.getKeyCode().
698         * @param event Description of the key event.
699         * @return If you handled the event, return {@code true}. If you want to allow the event to
700         *         be handled by the next receiver, return {@code false}.
701         */
702        @Override
703        public boolean onKeyUp(int keyCode, KeyEvent event) {
704            return false;
705        }
706
707        /**
708         * Implement this method to handle touch screen motion events on the current input session.
709         *
710         * @param event The motion event being received.
711         * @return If you handled the event, return {@code true}. If you want to allow the event to
712         *         be handled by the next receiver, return {@code false}.
713         * @see View#onTouchEvent
714         */
715        public boolean onTouchEvent(MotionEvent event) {
716            return false;
717        }
718
719        /**
720         * Implement this method to handle trackball events on the current input session.
721         *
722         * @param event The motion event being received.
723         * @return If you handled the event, return {@code true}. If you want to allow the event to
724         *         be handled by the next receiver, return {@code false}.
725         * @see View#onTrackballEvent
726         */
727        public boolean onTrackballEvent(MotionEvent event) {
728            return false;
729        }
730
731        /**
732         * Implement this method to handle generic motion events on the current input session.
733         *
734         * @param event The motion event being received.
735         * @return If you handled the event, return {@code true}. If you want to allow the event to
736         *         be handled by the next receiver, return {@code false}.
737         * @see View#onGenericMotionEvent
738         */
739        public boolean onGenericMotionEvent(MotionEvent event) {
740            return false;
741        }
742
743        /**
744         * This method is called when the application would like to stop using the current input
745         * session.
746         */
747        void release() {
748            removeOverlayView(true);
749            onRelease();
750            if (mSurface != null) {
751                mSurface.release();
752                mSurface = null;
753            }
754        }
755
756        /**
757         * Calls {@link #onSetMainSession}.
758         */
759        void setMainSession(boolean isMainSession) {
760            onSetMainSession(isMainSession);
761        }
762
763        /**
764         * Calls {@link #onSetSurface}.
765         */
766        void setSurface(Surface surface) {
767            onSetSurface(surface);
768            if (mSurface != null) {
769                mSurface.release();
770            }
771            mSurface = surface;
772            // TODO: Handle failure.
773        }
774
775        /**
776         * Calls {@link #onSurfaceChanged}.
777         */
778        void dispatchSurfaceChanged(int format, int width, int height) {
779            if (DEBUG) {
780                Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
781                        + ", height=" + height + ")");
782            }
783            onSurfaceChanged(format, width, height);
784        }
785
786        /**
787         * Calls {@link #onSetStreamVolume}.
788         */
789        void setStreamVolume(float volume) {
790            onSetStreamVolume(volume);
791        }
792
793        /**
794         * Calls {@link #onTune}.
795         */
796        void tune(Uri channelUri) {
797            onTune(channelUri);
798            // TODO: Handle failure.
799        }
800
801        /**
802         * Calls {@link #onSetCaptionEnabled}.
803         */
804        void setCaptionEnabled(boolean enabled) {
805            onSetCaptionEnabled(enabled);
806        }
807
808        /**
809         * Calls {@link #onSelectTrack}.
810         */
811        void selectTrack(TvTrackInfo track) {
812            onSelectTrack(track);
813            // TODO: Handle failure.
814        }
815
816        /**
817         * Calls {@link #onUnselectTrack}.
818         */
819        void unselectTrack(TvTrackInfo track) {
820            onUnselectTrack(track);
821            // TODO: Handle failure.
822        }
823
824        /**
825         * Calls {@link #onUnblockContent}.
826         */
827        void unblockContent(String unblockedRating) {
828            onUnblockContent(TvContentRating.unflattenFromString(unblockedRating));
829            // TODO: Handle failure.
830        }
831
832        /**
833         * Calls {@link #onAppPrivateCommand}.
834         */
835        void appPrivateCommand(String action, Bundle data) {
836            onAppPrivateCommand(action, data);
837        }
838
839        /**
840         * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
841         * to the overlay window.
842         *
843         * @param windowToken A window token of an application.
844         * @param frame A position of the overlay view.
845         */
846        void createOverlayView(IBinder windowToken, Rect frame) {
847            if (mOverlayView != null) {
848                mWindowManager.removeView(mOverlayView);
849                mOverlayView = null;
850            }
851            if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
852            mWindowToken = windowToken;
853            mOverlayFrame = frame;
854            if (!mOverlayViewEnabled) {
855                return;
856            }
857            mOverlayView = onCreateOverlayView();
858            if (mOverlayView == null) {
859                return;
860            }
861            // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
862            // an overlay window above the media window but below the application window.
863            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
864            // We make the overlay view non-focusable and non-touchable so that
865            // the application that owns the window token can decide whether to consume or
866            // dispatch the input events.
867            int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
868                    | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
869                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
870            mWindowParams = new WindowManager.LayoutParams(
871                    frame.right - frame.left, frame.bottom - frame.top,
872                    frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
873            mWindowParams.privateFlags |=
874                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
875            mWindowParams.gravity = Gravity.START | Gravity.TOP;
876            mWindowParams.token = windowToken;
877            mWindowManager.addView(mOverlayView, mWindowParams);
878        }
879
880        /**
881         * Relayouts the current overlay view.
882         *
883         * @param frame A new position of the overlay view.
884         */
885        void relayoutOverlayView(Rect frame) {
886            if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
887            mOverlayFrame = frame;
888            if (!mOverlayViewEnabled || mOverlayView == null) {
889                return;
890            }
891            mWindowParams.x = frame.left;
892            mWindowParams.y = frame.top;
893            mWindowParams.width = frame.right - frame.left;
894            mWindowParams.height = frame.bottom - frame.top;
895            mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
896        }
897
898        /**
899         * Removes the current overlay view.
900         */
901        void removeOverlayView(boolean clearWindowToken) {
902            if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
903            if (clearWindowToken) {
904                mWindowToken = null;
905                mOverlayFrame = null;
906            }
907            if (mOverlayView != null) {
908                mWindowManager.removeView(mOverlayView);
909                mOverlayView = null;
910                mWindowParams = null;
911            }
912        }
913
914        /**
915         * Takes care of dispatching incoming input events and tells whether the event was handled.
916         */
917        int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
918            if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
919            boolean isNavigationKey = false;
920            if (event instanceof KeyEvent) {
921                KeyEvent keyEvent = (KeyEvent) event;
922                isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
923                if (keyEvent.dispatch(this, mDispatcherState, this)) {
924                    return TvInputManager.Session.DISPATCH_HANDLED;
925                }
926            } else if (event instanceof MotionEvent) {
927                MotionEvent motionEvent = (MotionEvent) event;
928                final int source = motionEvent.getSource();
929                if (motionEvent.isTouchEvent()) {
930                    if (onTouchEvent(motionEvent)) {
931                        return TvInputManager.Session.DISPATCH_HANDLED;
932                    }
933                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
934                    if (onTrackballEvent(motionEvent)) {
935                        return TvInputManager.Session.DISPATCH_HANDLED;
936                    }
937                } else {
938                    if (onGenericMotionEvent(motionEvent)) {
939                        return TvInputManager.Session.DISPATCH_HANDLED;
940                    }
941                }
942            }
943            if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
944                return TvInputManager.Session.DISPATCH_NOT_HANDLED;
945            }
946            if (!mOverlayView.hasWindowFocus()) {
947                mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
948            }
949            if (isNavigationKey && mOverlayView.hasFocusable()) {
950                // If mOverlayView has focusable views, navigation key events should be always
951                // handled. If not, it can make the application UI navigation messed up.
952                // For example, in the case that the left-most view is focused, a left key event
953                // will not be handled in ViewRootImpl. Then, the left key event will be handled in
954                // the application during the UI navigation of the TV input.
955                mOverlayView.getViewRootImpl().dispatchInputEvent(event);
956                return TvInputManager.Session.DISPATCH_HANDLED;
957            } else {
958                mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
959                return TvInputManager.Session.DISPATCH_IN_PROGRESS;
960            }
961        }
962
963        private void setSessionCallback(ITvInputSessionCallback callback) {
964            mSessionCallback = callback;
965        }
966    }
967
968    /** @hide */
969    public static boolean isNavigationKey(int keyCode) {
970        switch (keyCode) {
971            case KeyEvent.KEYCODE_DPAD_LEFT:
972            case KeyEvent.KEYCODE_DPAD_RIGHT:
973            case KeyEvent.KEYCODE_DPAD_UP:
974            case KeyEvent.KEYCODE_DPAD_DOWN:
975            case KeyEvent.KEYCODE_DPAD_CENTER:
976            case KeyEvent.KEYCODE_PAGE_UP:
977            case KeyEvent.KEYCODE_PAGE_DOWN:
978            case KeyEvent.KEYCODE_MOVE_HOME:
979            case KeyEvent.KEYCODE_MOVE_END:
980            case KeyEvent.KEYCODE_TAB:
981            case KeyEvent.KEYCODE_SPACE:
982            case KeyEvent.KEYCODE_ENTER:
983                return true;
984        }
985        return false;
986    }
987
988    @SuppressLint("HandlerLeak")
989    private final class ServiceHandler extends Handler {
990        private static final int DO_CREATE_SESSION = 1;
991        private static final int DO_ADD_HARDWARE_TV_INPUT = 2;
992        private static final int DO_REMOVE_HARDWARE_TV_INPUT = 3;
993        private static final int DO_ADD_HDMI_CEC_TV_INPUT = 4;
994        private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 5;
995        private static final int DO_SET_WRAPPED_TV_INPUT_ID = 6;
996
997        private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
998            int n = mCallbacks.beginBroadcast();
999            for (int i = 0; i < n; ++i) {
1000                try {
1001                    mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo);
1002                } catch (RemoteException e) {
1003                    Log.e(TAG, "Error while broadcasting.", e);
1004                }
1005            }
1006            mCallbacks.finishBroadcast();
1007        }
1008
1009        private void broadcastAddHdmiCecTvInput(
1010                int logicalAddress, TvInputInfo inputInfo) {
1011            int n = mCallbacks.beginBroadcast();
1012            for (int i = 0; i < n; ++i) {
1013                try {
1014                    mCallbacks.getBroadcastItem(i).addHdmiCecTvInput(logicalAddress, inputInfo);
1015                } catch (RemoteException e) {
1016                    Log.e(TAG, "Error while broadcasting.", e);
1017                }
1018            }
1019            mCallbacks.finishBroadcast();
1020        }
1021
1022        private void broadcastRemoveTvInput(String inputId) {
1023            int n = mCallbacks.beginBroadcast();
1024            for (int i = 0; i < n; ++i) {
1025                try {
1026                    mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
1027                } catch (RemoteException e) {
1028                    Log.e(TAG, "Error while broadcasting.", e);
1029                }
1030            }
1031            mCallbacks.finishBroadcast();
1032        }
1033
1034        private void broadcastSetWrappedTvInputId(String inputId, String wrappedInputId) {
1035            int n = mCallbacks.beginBroadcast();
1036            for (int i = 0; i < n; ++i) {
1037                try {
1038                    mCallbacks.getBroadcastItem(i).setWrappedInputId(inputId, wrappedInputId);
1039                } catch (RemoteException e) {
1040                    Log.e(TAG, "Error while broadcasting.", e);
1041                }
1042            }
1043            mCallbacks.finishBroadcast();
1044        }
1045
1046        @Override
1047        public final void handleMessage(Message msg) {
1048            switch (msg.what) {
1049                case DO_CREATE_SESSION: {
1050                    SomeArgs args = (SomeArgs) msg.obj;
1051                    InputChannel channel = (InputChannel) args.arg1;
1052                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
1053                    String inputId = (String) args.arg3;
1054                    try {
1055                        Session sessionImpl = onCreateSession(inputId);
1056                        if (sessionImpl == null) {
1057                            // Failed to create a session.
1058                            cb.onSessionCreated(null);
1059                        } else {
1060                            sessionImpl.setSessionCallback(cb);
1061                            ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
1062                                    sessionImpl, channel);
1063                            cb.onSessionCreated(stub);
1064                        }
1065                    } catch (RemoteException e) {
1066                        Log.e(TAG, "error in onSessionCreated");
1067                    }
1068                    args.recycle();
1069                    return;
1070                }
1071                case DO_ADD_HARDWARE_TV_INPUT: {
1072                    TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1073                    TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
1074                    if (inputInfo != null) {
1075                        broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo);
1076                    }
1077                    return;
1078                }
1079                case DO_REMOVE_HARDWARE_TV_INPUT: {
1080                    TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1081                    String inputId = onHardwareRemoved(hardwareInfo);
1082                    if (inputId != null) {
1083                        broadcastRemoveTvInput(inputId);
1084                    }
1085                    return;
1086                }
1087                case DO_ADD_HDMI_CEC_TV_INPUT: {
1088                    HdmiCecDeviceInfo cecDeviceInfo = (HdmiCecDeviceInfo) msg.obj;
1089                    TvInputInfo inputInfo = onHdmiCecDeviceAdded(cecDeviceInfo);
1090                    if (inputInfo != null) {
1091                        broadcastAddHdmiCecTvInput(cecDeviceInfo.getLogicalAddress(), inputInfo);
1092                    }
1093                    return;
1094                }
1095                case DO_REMOVE_HDMI_CEC_TV_INPUT: {
1096                    HdmiCecDeviceInfo cecDeviceInfo = (HdmiCecDeviceInfo) msg.obj;
1097                    String inputId = onHdmiCecDeviceRemoved(cecDeviceInfo);
1098                    if (inputId != null) {
1099                        broadcastRemoveTvInput(inputId);
1100                    }
1101                    return;
1102                }
1103                case DO_SET_WRAPPED_TV_INPUT_ID: {
1104                    SomeArgs args = (SomeArgs) msg.obj;
1105                    String inputId = (String) args.arg1;
1106                    String wrappedInputId = (String) args.arg2;
1107                    broadcastSetWrappedTvInputId(inputId, wrappedInputId);
1108                    return;
1109                }
1110                default: {
1111                    Log.w(TAG, "Unhandled message code: " + msg.what);
1112                    return;
1113                }
1114            }
1115        }
1116    }
1117}
1118