2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.media.tv;
19 import android.annotation.SuppressLint;
20 import android.annotation.SystemApi;
21 import android.app.Service;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.graphics.PixelFormat;
25 import android.graphics.Rect;
26 import android.hardware.hdmi.HdmiCecDeviceInfo;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.os.RemoteCallbackList;
34 import android.os.RemoteException;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.Gravity;
38 import android.view.InputChannel;
39 import android.view.InputDevice;
40 import android.view.InputEvent;
41 import android.view.InputEventReceiver;
42 import android.view.KeyEvent;
43 import android.view.MotionEvent;
44 import android.view.Surface;
45 import android.view.SurfaceView;
46 import android.view.View;
47 import android.view.WindowManager;
48 import android.view.accessibility.CaptioningManager;
50 import com.android.internal.annotations.VisibleForTesting;
51 import com.android.internal.os.SomeArgs;
53 import java.util.HashSet;
54 import java.util.List;
58 * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
59 * provides pass-through video or broadcast TV programs.
61 * Applications will not normally use this service themselves, instead relying on the standard
62 * interaction provided by {@link TvView}. Those implementing TV input services should normally do
63 * so by deriving from this class and providing their own session implementation based on
64 * {@link TvInputService.Session}. All TV input services must require that clients hold the
65 * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this
66 * permission is not specified in the manifest, the system will refuse to bind to that TV input
70 public abstract class TvInputService extends Service {
71 // STOPSHIP: Turn debugging off.
72 private static final boolean DEBUG = true;
73 private static final String TAG = "TvInputService";
76 * This is the interface name that a service implementing a TV input should say that it support
77 * -- that is, this is the action it uses for its intent filter. To be supported, the service
78 * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
79 * other applications cannot abuse it.
81 public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
84 * Name under which a TvInputService component publishes information about itself.
85 * This meta-data must reference an XML resource containing an
86 * <code><{@link android.R.styleable#TvInputService tv-input}></code>
89 public static final String SERVICE_META_DATA = "android.media.tv.input";
92 * Handler instance to handle request from TV Input Manager Service. Should be run in the main
93 * looper to be synchronously run with {@code Session.mHandler}.
95 private Handler mServiceHandler = new ServiceHandler();
96 private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
97 new RemoteCallbackList<ITvInputServiceCallback>();
100 public final IBinder onBind(Intent intent) {
101 return new ITvInputService.Stub() {
103 public void registerCallback(ITvInputServiceCallback cb) {
105 mCallbacks.register(cb);
110 public void unregisterCallback(ITvInputServiceCallback cb) {
112 mCallbacks.unregister(cb);
117 public void createSession(InputChannel channel, ITvInputSessionCallback cb,
119 if (channel == null) {
120 Log.w(TAG, "Creating session without input channel");
125 SomeArgs args = SomeArgs.obtain();
129 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
133 public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
134 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT,
135 hardwareInfo).sendToTarget();
139 public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
140 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_TV_INPUT,
141 hardwareInfo).sendToTarget();
145 public void notifyHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
146 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_CEC_TV_INPUT,
147 cecDeviceInfo).sendToTarget();
151 public void notifyHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
152 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_CEC_TV_INPUT,
153 cecDeviceInfo).sendToTarget();
159 * Get the number of callbacks that are registered.
163 public final int getRegisteredCallbackCount() {
164 return mCallbacks.getRegisteredCallbackCount();
168 * Returns a concrete implementation of {@link Session}.
170 * May return {@code null} if this TV input service fails to create a session for some reason.
171 * If TV input represents an external device connected to a hardware TV input,
172 * {@link HardwareSession} should be returned.
174 * @param inputId The ID of the TV input associated with the session.
176 public abstract Session onCreateSession(String inputId);
179 * Returns a new {@link TvInputInfo} object if this service is responsible for
180 * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
181 * ignoring all hardware input.
183 * @param hardwareInfo {@link TvInputHardwareInfo} object just added.
187 public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
192 * Returns the input ID for {@code deviceId} if it is handled by this service;
193 * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware
196 * @param hardwareInfo {@link TvInputHardwareInfo} object just removed.
200 public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
205 * Returns a new {@link TvInputInfo} object if this service is responsible for
206 * {@code cecDeviceInfo}; otherwise, return {@code null}. Override to modify default behavior
207 * of ignoring all HDMI CEC logical input device.
209 * @param cecDeviceInfo {@link HdmiCecDeviceInfo} object just added.
213 public TvInputInfo onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDeviceInfo) {
218 * Returns the input ID for {@code logicalAddress} if it is handled by this service;
219 * otherwise, return {@code null}. Override to modify default behavior of ignoring all HDMI CEC
220 * logical input device.
222 * @param cecDeviceInfo {@link HdmiCecDeviceInfo} object just removed.
226 public String onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDeviceInfo) {
231 * Base class for derived classes to implement to provide a TV input session.
233 public abstract static class Session implements KeyEvent.Callback {
234 private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
235 private final WindowManager mWindowManager;
236 final Handler mHandler;
237 private WindowManager.LayoutParams mWindowParams;
238 private Surface mSurface;
239 private View mOverlayView;
240 private boolean mOverlayViewEnabled;
241 private IBinder mWindowToken;
242 private Rect mOverlayFrame;
243 private ITvInputSessionCallback mSessionCallback;
246 * Creates a new Session.
248 * @param context The context of the application
250 public Session(Context context) {
251 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
252 mHandler = new Handler(context.getMainLooper());
256 * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
257 * called explicitly after the session is created to enable the overlay view.
259 * @param enable {@code true} if you want to enable the overlay view. {@code false}
262 public void setOverlayViewEnabled(final boolean enable) {
263 mHandler.post(new Runnable() {
266 if (enable == mOverlayViewEnabled) {
269 mOverlayViewEnabled = enable;
271 if (mWindowToken != null) {
272 createOverlayView(mWindowToken, mOverlayFrame);
275 removeOverlayView(false);
282 * Dispatches an event to the application using this session.
284 * @param eventType The type of the event.
285 * @param eventArgs Optional arguments of the event.
288 public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
289 if (eventType == null) {
290 throw new IllegalArgumentException("eventType should not be null.");
292 mHandler.post(new Runnable() {
296 if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
297 mSessionCallback.onSessionEvent(eventType, eventArgs);
298 } catch (RemoteException e) {
299 Log.w(TAG, "error in sending event (event=" + eventType + ")");
306 * Notifies the channel of the session is retuned by TV input.
308 * @param channelUri The URI of a channel.
310 public void notifyChannelRetuned(final Uri channelUri) {
311 mHandler.post(new Runnable() {
315 if (DEBUG) Log.d(TAG, "notifyChannelRetuned");
316 mSessionCallback.onChannelRetuned(channelUri);
317 } catch (RemoteException e) {
318 Log.w(TAG, "error in notifyChannelRetuned");
325 * Sends the change on the track information. This is expected to be called whenever a track
326 * is added/removed and the metadata of a track is modified.
328 * @param tracks A list which includes track information.
329 * @throws IllegalArgumentException if {@code tracks} contains redundant tracks.
331 public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
332 Set<String> trackIdSet = new HashSet<String>();
333 for (TvTrackInfo track : tracks) {
334 String trackId = track.getId();
335 if (trackIdSet.contains(trackId)) {
336 throw new IllegalArgumentException("redundant track ID: " + trackId);
338 trackIdSet.add(trackId);
342 // TODO: Validate the track list.
343 mHandler.post(new Runnable() {
347 if (DEBUG) Log.d(TAG, "notifyTracksChanged");
348 mSessionCallback.onTracksChanged(tracks);
349 } catch (RemoteException e) {
350 Log.w(TAG, "error in notifyTracksChanged");
357 * Sends the ID of the selected track for a given track type. This is expected to be called
358 * whenever there is a change on track selection.
360 * @param type The type of the selected track. The type can be
361 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
362 * {@link TvTrackInfo#TYPE_SUBTITLE}.
363 * @param trackId The ID of the selected track.
364 * @see #onSelectTrack
366 public void notifyTrackSelected(final int type, final String trackId) {
367 mHandler.post(new Runnable() {
371 if (DEBUG) Log.d(TAG, "notifyTrackSelected");
372 mSessionCallback.onTrackSelected(type, trackId);
373 } catch (RemoteException e) {
374 Log.w(TAG, "error in notifyTrackSelected");
381 * Informs the application that video is available and the playback of the TV stream has
384 public void notifyVideoAvailable() {
385 mHandler.post(new Runnable() {
389 if (DEBUG) Log.d(TAG, "notifyVideoAvailable");
390 mSessionCallback.onVideoAvailable();
391 } catch (RemoteException e) {
392 Log.w(TAG, "error in notifyVideoAvailable");
399 * Informs the application that video is not available, so the TV input cannot continue
400 * playing the TV stream.
402 * @param reason The reason that the TV input stopped the playback:
404 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
405 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
406 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
407 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
410 public void notifyVideoUnavailable(final int reason) {
411 if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
412 || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
413 throw new IllegalArgumentException("Unknown reason: " + reason);
415 mHandler.post(new Runnable() {
419 if (DEBUG) Log.d(TAG, "notifyVideoUnavailable");
420 mSessionCallback.onVideoUnavailable(reason);
421 } catch (RemoteException e) {
422 Log.w(TAG, "error in notifyVideoUnavailable");
429 * Informs the application that the user is allowed to watch the current program content.
431 * Each TV input service is required to query the system whether the user is allowed to
432 * watch the current program before showing it to the user if the parental controls is
433 * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
434 * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
435 * service should block the content or not is determined by invoking
436 * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
437 * with the content rating for the current program. Then the {@link TvInputManager} makes a
438 * judgment based on the user blocked ratings stored in the secure settings and returns the
439 * result. If the rating in question turns out to be allowed by the user, the TV input
440 * service must call this method to notify the application that is permitted to show the
443 * Each TV input service also needs to continuously listen to any changes made to the
444 * parental controls settings by registering a broadcast receiver to receive
445 * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
446 * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
447 * reevaluate the current program with the new parental controls settings.
450 * @see #notifyContentBlocked
451 * @see TvInputManager
453 public void notifyContentAllowed() {
454 mHandler.post(new Runnable() {
458 if (DEBUG) Log.d(TAG, "notifyContentAllowed");
459 mSessionCallback.onContentAllowed();
460 } catch (RemoteException e) {
461 Log.w(TAG, "error in notifyContentAllowed");
468 * Informs the application that the current program content is blocked by parent controls.
470 * Each TV input service is required to query the system whether the user is allowed to
471 * watch the current program before showing it to the user if the parental controls is
472 * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
473 * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
474 * service should block the content or not is determined by invoking
475 * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
476 * with the content rating for the current program. Then the {@link TvInputManager} makes a
477 * judgment based on the user blocked ratings stored in the secure settings and returns the
478 * result. If the rating in question turns out to be blocked, the TV input service must
479 * immediately block the content and call this method with the content rating of the current
480 * program to prompt the PIN verification screen.
482 * Each TV input service also needs to continuously listen to any changes made to the
483 * parental controls settings by registering a broadcast receiver to receive
484 * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
485 * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
486 * reevaluate the current program with the new parental controls settings.
489 * @param rating The content rating for the current TV program.
490 * @see #notifyContentAllowed
491 * @see TvInputManager
493 public void notifyContentBlocked(final TvContentRating rating) {
494 mHandler.post(new Runnable() {
498 if (DEBUG) Log.d(TAG, "notifyContentBlocked");
499 mSessionCallback.onContentBlocked(rating.flattenToString());
500 } catch (RemoteException e) {
501 Log.w(TAG, "error in notifyContentBlocked");
508 * Assigns a position of the {@link Surface} passed by {@link #onSetSurface}. The position
509 * is relative to an overlay view. {@see #onOverlayViewSizeChanged}.
511 * @param left Left position in pixels, relative to the overlay view.
512 * @param top Top position in pixels, relative to the overlay view.
513 * @param right Right position in pixels, relative to the overlay view.
514 * @param bottm Bottom position in pixels, relative to the overlay view.
518 public void layoutSurface(final int left, final int top, final int right, final int bottm) {
519 if (left > right || top > bottm) {
520 throw new IllegalArgumentException("Invalid parameter");
522 mHandler.post(new Runnable() {
526 if (DEBUG) Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top + ", r="
527 + right + ", b=" + bottm + ",)");
528 mSessionCallback.onLayoutSurface(left, top, right, bottm);
529 } catch (RemoteException e) {
530 Log.w(TAG, "error in layoutSurface");
537 * Called when the session is released.
539 public abstract void onRelease();
542 * Set the current session as the "main" session. See {@link TvView#setMainTvView} for the
545 * This is primarily for HDMI-CEC active source management. TV input service that manages
546 * HDMI-CEC logical device should make sure not only to select the corresponding HDMI
547 * logical device as source device on {@code onSetMainSession(true)}, but also to select
548 * internal device on {@code onSetMainSession(false)}. Also, if surface is set to non-main
549 * session, it needs to select internal device after temporarily selecting corresponding
550 * HDMI logical device for set up.
552 * It is guaranteed that {@code onSetMainSession(true)} for new session is called first,
553 * and {@code onSetMainSession(false)} for old session is called afterwards. This allows
554 * {@code onSetMainSession(false)} to be no-op when TV input service knows that the next
555 * main session corresponds to another HDMI logical device. Practically, this implies that
556 * one TV input service should handle all HDMI port and HDMI-CEC logical devices for smooth
557 * active source transition.
560 * @param isMainSession If true, session is main.
564 public void onSetMainSession(boolean isMainSession) {
568 * Sets the {@link Surface} for the current input session on which the TV input renders
571 * @param surface {@link Surface} an application passes to this TV input session.
572 * @return {@code true} if the surface was set, {@code false} otherwise.
574 public abstract boolean onSetSurface(Surface surface);
577 * Called after any structural changes (format or size) have been made to the
578 * {@link Surface} passed by {@link #onSetSurface}. This method is always called
579 * at least once, after {@link #onSetSurface} with non-null {@link Surface} is called.
581 * @param format The new PixelFormat of the {@link Surface}.
582 * @param width The new width of the {@link Surface}.
583 * @param height The new height of the {@link Surface}.
585 public void onSurfaceChanged(int format, int width, int height) {
589 * Called when a size of an overlay view is changed by an application. Even when the overlay
590 * view is disabled by {@link #setOverlayViewEnabled}, this is called. The size is same as
591 * the size of {@link Surface} in general. Once {@link #layoutSurface} is called, the sizes
592 * of {@link Surface} and the overlay view can be different.
594 * @param width The width of the overlay view.
595 * @param height The height of the overlay view.
599 public void onOverlayViewSizeChanged(int width, int height) {
603 * Sets the relative stream volume of the current TV input session to handle the change of
604 * audio focus by setting.
606 * @param volume Volume scale from 0.0 to 1.0.
608 public abstract void onSetStreamVolume(float volume);
611 * Tunes to a given channel. When the video is available, {@link #notifyVideoAvailable()}
612 * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the
613 * TV input cannot continue playing the given channel.
615 * @param channelUri The URI of the channel.
616 * @return {@code true} the tuning was successful, {@code false} otherwise.
618 public abstract boolean onTune(Uri channelUri);
621 * Calls {@link #onTune(Uri)}. Override this method in order to handle {@code params}.
623 * @param channelUri The URI of the channel.
624 * @param params The extra parameters from other applications.
625 * @return {@code true} the tuning was successful, {@code false} otherwise.
629 public boolean onTune(Uri channelUri, Bundle params) {
630 return onTune(channelUri);
634 * Enables or disables the caption.
636 * The locale for the user's preferred captioning language can be obtained by calling
637 * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
639 * @param enabled {@code true} to enable, {@code false} to disable.
640 * @see CaptioningManager
642 public abstract void onSetCaptionEnabled(boolean enabled);
645 * Requests to unblock the content according to the given rating.
647 * The implementation should unblock the content.
648 * TV input service has responsibility to decide when/how the unblock expires
649 * while it can keep previously unblocked ratings in order not to ask a user
650 * to unblock whenever a content rating is changed.
651 * Therefore an unblocked rating can be valid for a channel, a program,
652 * or certain amount of time depending on the implementation.
655 * @param unblockedRating An unblocked content rating
657 public void onUnblockContent(TvContentRating unblockedRating) {
661 * Select a given track.
663 * If this is done successfully, the implementation should call {@link #notifyTrackSelected}
664 * to help applications maintain the selcted track lists.
667 * @param trackId The ID of the track to select. {@code null} means to unselect the current
668 * track for a given type.
669 * @param type The type of the track to select. The type can be
670 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
671 * {@link TvTrackInfo#TYPE_SUBTITLE}.
672 * @see #notifyTrackSelected
674 public boolean onSelectTrack(int type, String trackId) {
679 * Processes a private command sent from the application to the TV input. This can be used
680 * to provide domain-specific features that are only known between certain TV inputs and
683 * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
684 * i.e. prefixed with a package name you own, so that different developers will
685 * not create conflicting commands.
686 * @param data Any data to include with the command.
690 public void onAppPrivateCommand(String action, Bundle data) {
694 * Called when an application requests to create an overlay view. Each session
695 * implementation can override this method and return its own view.
697 * @return a view attached to the overlay window
699 public View onCreateOverlayView() {
704 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
705 * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
707 * Override this to intercept key down events before they are processed by the application.
708 * If you return true, the application will not process the event itself. If you return
709 * false, the normal application processing will occur as if the TV input had not seen the
712 * @param keyCode The value in event.getKeyCode().
713 * @param event Description of the key event.
714 * @return If you handled the event, return {@code true}. If you want to allow the event to
715 * be handled by the next receiver, return {@code false}.
718 public boolean onKeyDown(int keyCode, KeyEvent event) {
723 * Default implementation of
724 * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
725 * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
727 * Override this to intercept key long press events before they are processed by the
728 * application. If you return true, the application will not process the event itself. If
729 * you return false, the normal application processing will occur as if the TV input had not
730 * seen the event at all.
732 * @param keyCode The value in event.getKeyCode().
733 * @param event Description of the key event.
734 * @return If you handled the event, return {@code true}. If you want to allow the event to
735 * be handled by the next receiver, return {@code false}.
738 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
743 * Default implementation of
744 * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
745 * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
747 * Override this to intercept special key multiple events before they are processed by the
748 * application. If you return true, the application will not itself process the event. If
749 * you return false, the normal application processing will occur as if the TV input had not
750 * seen the event at all.
752 * @param keyCode The value in event.getKeyCode().
753 * @param count The number of times the action was made.
754 * @param event Description of the key event.
755 * @return If you handled the event, return {@code true}. If you want to allow the event to
756 * be handled by the next receiver, return {@code false}.
759 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
764 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
765 * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
767 * Override this to intercept key up events before they are processed by the application. If
768 * you return true, the application will not itself process the event. If you return false,
769 * the normal application processing will occur as if the TV input had not seen the event at
772 * @param keyCode The value in event.getKeyCode().
773 * @param event Description of the key event.
774 * @return If you handled the event, return {@code true}. If you want to allow the event to
775 * be handled by the next receiver, return {@code false}.
778 public boolean onKeyUp(int keyCode, KeyEvent event) {
783 * Implement this method to handle touch screen motion events on the current input session.
785 * @param event The motion event being received.
786 * @return If you handled the event, return {@code true}. If you want to allow the event to
787 * be handled by the next receiver, return {@code false}.
788 * @see View#onTouchEvent
790 public boolean onTouchEvent(MotionEvent event) {
795 * Implement this method to handle trackball events on the current input session.
797 * @param event The motion event being received.
798 * @return If you handled the event, return {@code true}. If you want to allow the event to
799 * be handled by the next receiver, return {@code false}.
800 * @see View#onTrackballEvent
802 public boolean onTrackballEvent(MotionEvent event) {
807 * Implement this method to handle generic motion events on the current input session.
809 * @param event The motion event being received.
810 * @return If you handled the event, return {@code true}. If you want to allow the event to
811 * be handled by the next receiver, return {@code false}.
812 * @see View#onGenericMotionEvent
814 public boolean onGenericMotionEvent(MotionEvent event) {
819 * This method is called when the application would like to stop using the current input
823 removeOverlayView(true);
825 if (mSurface != null) {
832 * Calls {@link #onSetMainSession}.
834 void setMainSession(boolean isMainSession) {
835 onSetMainSession(isMainSession);
839 * Calls {@link #onSetSurface}.
841 void setSurface(Surface surface) {
842 onSetSurface(surface);
843 if (mSurface != null) {
847 // TODO: Handle failure.
851 * Calls {@link #onSurfaceChanged}.
853 void dispatchSurfaceChanged(int format, int width, int height) {
855 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
856 + ", height=" + height + ")");
858 onSurfaceChanged(format, width, height);
862 * Calls {@link #onSetStreamVolume}.
864 void setStreamVolume(float volume) {
865 onSetStreamVolume(volume);
869 * Calls {@link #onTune}.
871 void tune(Uri channelUri, Bundle params) {
872 onTune(channelUri, params);
873 // TODO: Handle failure.
877 * Calls {@link #onSetCaptionEnabled}.
879 void setCaptionEnabled(boolean enabled) {
880 onSetCaptionEnabled(enabled);
884 * Calls {@link #onSelectTrack}.
886 void selectTrack(int type, String trackId) {
887 onSelectTrack(type, trackId);
891 * Calls {@link #onUnblockContent}.
893 void unblockContent(String unblockedRating) {
894 onUnblockContent(TvContentRating.unflattenFromString(unblockedRating));
895 // TODO: Handle failure.
899 * Calls {@link #onAppPrivateCommand}.
901 void appPrivateCommand(String action, Bundle data) {
902 onAppPrivateCommand(action, data);
906 * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
907 * to the overlay window.
909 * @param windowToken A window token of an application.
910 * @param frame A position of the overlay view.
912 void createOverlayView(IBinder windowToken, Rect frame) {
913 if (mOverlayView != null) {
914 mWindowManager.removeView(mOverlayView);
917 if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
918 mWindowToken = windowToken;
919 mOverlayFrame = frame;
920 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
921 if (!mOverlayViewEnabled) {
924 mOverlayView = onCreateOverlayView();
925 if (mOverlayView == null) {
928 // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
929 // an overlay window above the media window but below the application window.
930 int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
931 // We make the overlay view non-focusable and non-touchable so that
932 // the application that owns the window token can decide whether to consume or
933 // dispatch the input events.
934 int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
935 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
936 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
937 mWindowParams = new WindowManager.LayoutParams(
938 frame.right - frame.left, frame.bottom - frame.top,
939 frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
940 mWindowParams.privateFlags |=
941 WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
942 mWindowParams.gravity = Gravity.START | Gravity.TOP;
943 mWindowParams.token = windowToken;
944 mWindowManager.addView(mOverlayView, mWindowParams);
948 * Relayouts the current overlay view.
950 * @param frame A new position of the overlay view.
952 void relayoutOverlayView(Rect frame) {
953 if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
954 if (mOverlayFrame == null || mOverlayFrame.width() != frame.width()
955 || mOverlayFrame.height() != frame.height()) {
956 // Note: relayoutOverlayView is called whenever TvView's layout is changed
957 // regardless of setOverlayViewEnabled.
958 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
960 mOverlayFrame = frame;
961 if (!mOverlayViewEnabled || mOverlayView == null) {
964 mWindowParams.x = frame.left;
965 mWindowParams.y = frame.top;
966 mWindowParams.width = frame.right - frame.left;
967 mWindowParams.height = frame.bottom - frame.top;
968 mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
972 * Removes the current overlay view.
974 void removeOverlayView(boolean clearWindowToken) {
975 if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
976 if (clearWindowToken) {
978 mOverlayFrame = null;
980 if (mOverlayView != null) {
981 mWindowManager.removeView(mOverlayView);
983 mWindowParams = null;
988 * Takes care of dispatching incoming input events and tells whether the event was handled.
990 int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
991 if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
992 boolean isNavigationKey = false;
993 if (event instanceof KeyEvent) {
994 KeyEvent keyEvent = (KeyEvent) event;
995 isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
996 if (keyEvent.dispatch(this, mDispatcherState, this)) {
997 return TvInputManager.Session.DISPATCH_HANDLED;
999 } else if (event instanceof MotionEvent) {
1000 MotionEvent motionEvent = (MotionEvent) event;
1001 final int source = motionEvent.getSource();
1002 if (motionEvent.isTouchEvent()) {
1003 if (onTouchEvent(motionEvent)) {
1004 return TvInputManager.Session.DISPATCH_HANDLED;
1006 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
1007 if (onTrackballEvent(motionEvent)) {
1008 return TvInputManager.Session.DISPATCH_HANDLED;
1011 if (onGenericMotionEvent(motionEvent)) {
1012 return TvInputManager.Session.DISPATCH_HANDLED;
1016 if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
1017 return TvInputManager.Session.DISPATCH_NOT_HANDLED;
1019 if (!mOverlayView.hasWindowFocus()) {
1020 mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
1022 if (isNavigationKey && mOverlayView.hasFocusable()) {
1023 // If mOverlayView has focusable views, navigation key events should be always
1024 // handled. If not, it can make the application UI navigation messed up.
1025 // For example, in the case that the left-most view is focused, a left key event
1026 // will not be handled in ViewRootImpl. Then, the left key event will be handled in
1027 // the application during the UI navigation of the TV input.
1028 mOverlayView.getViewRootImpl().dispatchInputEvent(event);
1029 return TvInputManager.Session.DISPATCH_HANDLED;
1031 mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
1032 return TvInputManager.Session.DISPATCH_IN_PROGRESS;
1036 private void setSessionCallback(ITvInputSessionCallback callback) {
1037 mSessionCallback = callback;
1042 * Base class for a TV input session which represents an external device connected to a
1043 * hardware TV input. Once TV input returns an implementation of this class on
1044 * {@link #onCreateSession(String)}, the framework will create a hardware session and forward
1045 * the application's surface to the hardware TV input.
1046 * @see #onCreateSession(String)
1048 public abstract static class HardwareSession extends Session {
1051 * Creates a new HardwareSession.
1053 * @param context The context of the application
1055 public HardwareSession(Context context) {
1059 private TvInputManager.Session mHardwareSession;
1060 private ITvInputSession mProxySession;
1061 private ITvInputSessionCallback mProxySessionCallback;
1064 * Returns the hardware TV input ID the external device is connected to.
1066 * TV input is expected to provide {@link android.R.attr#setupActivity} so that
1067 * the application can launch it before using this TV input. The setup activity may let
1068 * the user select the hardware TV input to which the external device is connected. The ID
1069 * of the selected one should be stored in the TV input so that it can be returned here.
1072 public abstract String getHardwareInputId();
1074 private final TvInputManager.SessionCallback mHardwareSessionCallback =
1075 new TvInputManager.SessionCallback() {
1077 public void onSessionCreated(TvInputManager.Session session) {
1078 mHardwareSession = session;
1079 SomeArgs args = SomeArgs.obtain();
1080 if (session != null) {
1081 args.arg1 = mProxySession;
1082 args.arg2 = mProxySessionCallback;
1083 args.arg3 = session.getToken();
1086 args.arg2 = mProxySessionCallback;
1090 mHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
1095 public void onVideoAvailable(final TvInputManager.Session session) {
1096 if (mHardwareSession == session) {
1097 onHardwareVideoAvailable();
1102 public void onVideoUnavailable(final TvInputManager.Session session,
1104 if (mHardwareSession == session) {
1105 onHardwareVideoUnavailable(reason);
1111 * This method will not be called in {@link HardwareSession}. Framework will
1112 * forward the application's surface to the hardware TV input.
1115 public final boolean onSetSurface(Surface surface) {
1116 Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
1121 * Called when the underlying hardware TV input session calls
1122 * {@link TvInputService.Session#notifyVideoAvailable()}.
1124 public void onHardwareVideoAvailable() { }
1127 * Called when the underlying hardware TV input session calls
1128 * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
1130 * @param reason The reason that the hardware TV input stopped the playback:
1132 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
1133 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
1134 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
1135 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
1138 public void onHardwareVideoUnavailable(int reason) { }
1142 public static boolean isNavigationKey(int keyCode) {
1144 case KeyEvent.KEYCODE_DPAD_LEFT:
1145 case KeyEvent.KEYCODE_DPAD_RIGHT:
1146 case KeyEvent.KEYCODE_DPAD_UP:
1147 case KeyEvent.KEYCODE_DPAD_DOWN:
1148 case KeyEvent.KEYCODE_DPAD_CENTER:
1149 case KeyEvent.KEYCODE_PAGE_UP:
1150 case KeyEvent.KEYCODE_PAGE_DOWN:
1151 case KeyEvent.KEYCODE_MOVE_HOME:
1152 case KeyEvent.KEYCODE_MOVE_END:
1153 case KeyEvent.KEYCODE_TAB:
1154 case KeyEvent.KEYCODE_SPACE:
1155 case KeyEvent.KEYCODE_ENTER:
1161 @SuppressLint("HandlerLeak")
1162 private final class ServiceHandler extends Handler {
1163 private static final int DO_CREATE_SESSION = 1;
1164 private static final int DO_NOTIFY_SESSION_CREATED = 2;
1165 private static final int DO_ADD_HARDWARE_TV_INPUT = 3;
1166 private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4;
1167 private static final int DO_ADD_HDMI_CEC_TV_INPUT = 5;
1168 private static final int DO_REMOVE_HDMI_CEC_TV_INPUT = 6;
1170 private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1171 int n = mCallbacks.beginBroadcast();
1172 for (int i = 0; i < n; ++i) {
1174 mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo);
1175 } catch (RemoteException e) {
1176 Log.e(TAG, "Error while broadcasting.", e);
1179 mCallbacks.finishBroadcast();
1182 private void broadcastAddHdmiCecTvInput(
1183 int logicalAddress, TvInputInfo inputInfo) {
1184 int n = mCallbacks.beginBroadcast();
1185 for (int i = 0; i < n; ++i) {
1187 mCallbacks.getBroadcastItem(i).addHdmiCecTvInput(logicalAddress, inputInfo);
1188 } catch (RemoteException e) {
1189 Log.e(TAG, "Error while broadcasting.", e);
1192 mCallbacks.finishBroadcast();
1195 private void broadcastRemoveTvInput(String inputId) {
1196 int n = mCallbacks.beginBroadcast();
1197 for (int i = 0; i < n; ++i) {
1199 mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
1200 } catch (RemoteException e) {
1201 Log.e(TAG, "Error while broadcasting.", e);
1204 mCallbacks.finishBroadcast();
1208 public final void handleMessage(Message msg) {
1210 case DO_CREATE_SESSION: {
1211 SomeArgs args = (SomeArgs) msg.obj;
1212 InputChannel channel = (InputChannel) args.arg1;
1213 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
1214 String inputId = (String) args.arg3;
1215 Session sessionImpl = onCreateSession(inputId);
1217 if (sessionImpl == null) {
1219 // Failed to create a session.
1220 cb.onSessionCreated(null, null);
1221 } catch (RemoteException e) {
1222 Log.e(TAG, "error in onSessionCreated");
1225 sessionImpl.setSessionCallback(cb);
1226 ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
1227 sessionImpl, channel);
1228 if (sessionImpl instanceof HardwareSession) {
1229 HardwareSession proxySession =
1230 ((HardwareSession) sessionImpl);
1231 String harewareInputId = proxySession.getHardwareInputId();
1232 if (!TextUtils.isEmpty(harewareInputId)) {
1233 // TODO: check if the given ID is really hardware TV input.
1234 proxySession.mProxySession = stub;
1235 proxySession.mProxySessionCallback = cb;
1236 TvInputManager manager = (TvInputManager) getSystemService(
1237 Context.TV_INPUT_SERVICE);
1238 manager.createSession(harewareInputId,
1239 proxySession.mHardwareSessionCallback, mServiceHandler);
1241 sessionImpl.onRelease();
1242 Log.w(TAG, "Hardware input id is not setup yet.");
1244 cb.onSessionCreated(null, null);
1245 } catch (RemoteException e) {
1246 Log.e(TAG, "error in onSessionCreated");
1250 SomeArgs someArgs = SomeArgs.obtain();
1251 someArgs.arg1 = stub;
1253 someArgs.arg3 = null;
1254 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
1255 someArgs).sendToTarget();
1260 case DO_NOTIFY_SESSION_CREATED: {
1261 SomeArgs args = (SomeArgs) msg.obj;
1262 ITvInputSession stub = (ITvInputSession) args.arg1;
1263 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
1264 IBinder hardwareSessionToken = (IBinder) args.arg3;
1266 cb.onSessionCreated(stub, hardwareSessionToken);
1267 } catch (RemoteException e) {
1268 Log.e(TAG, "error in onSessionCreated");
1273 case DO_ADD_HARDWARE_TV_INPUT: {
1274 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1275 TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
1276 if (inputInfo != null) {
1277 broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo);
1281 case DO_REMOVE_HARDWARE_TV_INPUT: {
1282 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1283 String inputId = onHardwareRemoved(hardwareInfo);
1284 if (inputId != null) {
1285 broadcastRemoveTvInput(inputId);
1289 case DO_ADD_HDMI_CEC_TV_INPUT: {
1290 HdmiCecDeviceInfo cecDeviceInfo = (HdmiCecDeviceInfo) msg.obj;
1291 TvInputInfo inputInfo = onHdmiCecDeviceAdded(cecDeviceInfo);
1292 if (inputInfo != null) {
1293 broadcastAddHdmiCecTvInput(cecDeviceInfo.getLogicalAddress(), inputInfo);
1297 case DO_REMOVE_HDMI_CEC_TV_INPUT: {
1298 HdmiCecDeviceInfo cecDeviceInfo = (HdmiCecDeviceInfo) msg.obj;
1299 String inputId = onHdmiCecDeviceRemoved(cecDeviceInfo);
1300 if (inputId != null) {
1301 broadcastRemoveTvInput(inputId);
1306 Log.w(TAG, "Unhandled message code: " + msg.what);