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.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SuppressLint;
22 import android.annotation.SystemApi;
23 import android.app.Service;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.PixelFormat;
27 import android.graphics.Rect;
28 import android.hardware.hdmi.HdmiDeviceInfo;
29 import android.net.Uri;
30 import android.os.AsyncTask;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.Message;
35 import android.os.Process;
36 import android.os.RemoteCallbackList;
37 import android.os.RemoteException;
38 import android.text.TextUtils;
39 import android.util.Log;
40 import android.view.Gravity;
41 import android.view.InputChannel;
42 import android.view.InputDevice;
43 import android.view.InputEvent;
44 import android.view.InputEventReceiver;
45 import android.view.KeyEvent;
46 import android.view.MotionEvent;
47 import android.view.Surface;
48 import android.view.View;
49 import android.view.WindowManager;
50 import android.view.accessibility.CaptioningManager;
51 import android.widget.FrameLayout;
53 import com.android.internal.os.SomeArgs;
55 import java.util.ArrayList;
56 import java.util.HashSet;
57 import java.util.List;
61 * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
62 * provides pass-through video or broadcast TV programs.
64 * <p>Applications will not normally use this service themselves, instead relying on the standard
65 * interaction provided by {@link TvView}. Those implementing TV input services should normally do
66 * so by deriving from this class and providing their own session implementation based on
67 * {@link TvInputService.Session}. All TV input services must require that clients hold the
68 * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this
69 * permission is not specified in the manifest, the system will refuse to bind to that TV input
72 public abstract class TvInputService extends Service {
73 private static final boolean DEBUG = false;
74 private static final String TAG = "TvInputService";
77 * This is the interface name that a service implementing a TV input should say that it support
78 * -- that is, this is the action it uses for its intent filter. To be supported, the service
79 * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
80 * other applications cannot abuse it.
82 public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
85 * Name under which a TvInputService component publishes information about itself.
86 * This meta-data must reference an XML resource containing an
87 * <code><{@link android.R.styleable#TvInputService tv-input}></code>
90 public static final String SERVICE_META_DATA = "android.media.tv.input";
93 * Handler instance to handle request from TV Input Manager Service. Should be run in the main
94 * looper to be synchronously run with {@code Session.mHandler}.
96 private final Handler mServiceHandler = new ServiceHandler();
97 private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
98 new RemoteCallbackList<ITvInputServiceCallback>();
100 private TvInputManager mTvInputManager;
103 public final IBinder onBind(Intent intent) {
104 return new ITvInputService.Stub() {
106 public void registerCallback(ITvInputServiceCallback cb) {
108 mCallbacks.register(cb);
113 public void unregisterCallback(ITvInputServiceCallback cb) {
115 mCallbacks.unregister(cb);
120 public void createSession(InputChannel channel, ITvInputSessionCallback cb,
122 if (channel == null) {
123 Log.w(TAG, "Creating session without input channel");
128 SomeArgs args = SomeArgs.obtain();
132 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
136 public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
137 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT,
138 hardwareInfo).sendToTarget();
142 public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
143 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_TV_INPUT,
144 hardwareInfo).sendToTarget();
148 public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
149 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_TV_INPUT,
150 deviceInfo).sendToTarget();
154 public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
155 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_TV_INPUT,
156 deviceInfo).sendToTarget();
162 * Returns a concrete implementation of {@link Session}.
164 * <p>May return {@code null} if this TV input service fails to create a session for some
165 * reason. If TV input represents an external device connected to a hardware TV input,
166 * {@link HardwareSession} should be returned.
168 * @param inputId The ID of the TV input associated with the session.
171 public abstract Session onCreateSession(String inputId);
174 * Returns a new {@link TvInputInfo} object if this service is responsible for
175 * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
176 * ignoring all hardware input.
178 * @param hardwareInfo {@link TvInputHardwareInfo} object just added.
183 public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
188 * Returns the input ID for {@code deviceId} if it is handled by this service;
189 * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware
192 * @param hardwareInfo {@link TvInputHardwareInfo} object just removed.
197 public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
202 * Returns a new {@link TvInputInfo} object if this service is responsible for
203 * {@code deviceInfo}; otherwise, return {@code null}. Override to modify default behavior of
204 * ignoring all HDMI logical input device.
206 * @param deviceInfo {@link HdmiDeviceInfo} object just added.
211 public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
216 * Returns the input ID for {@code deviceInfo} if it is handled by this service; otherwise,
217 * return {@code null}. Override to modify default behavior of ignoring all HDMI logical input
220 * @param deviceInfo {@link HdmiDeviceInfo} object just removed.
225 public String onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
229 private boolean isPassthroughInput(String inputId) {
230 if (mTvInputManager == null) {
231 mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
233 TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
234 if (info != null && info.isPassthroughInput()) {
241 * Base class for derived classes to implement to provide a TV input session.
243 public abstract static class Session implements KeyEvent.Callback {
244 private static final int DETACH_OVERLAY_VIEW_TIMEOUT_MS = 5000;
245 private static final int POSITION_UPDATE_INTERVAL_MS = 1000;
247 private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
248 private final WindowManager mWindowManager;
249 final Handler mHandler;
250 private WindowManager.LayoutParams mWindowParams;
251 private Surface mSurface;
252 private final Context mContext;
253 private FrameLayout mOverlayViewContainer;
254 private View mOverlayView;
255 private OverlayViewCleanUpTask mOverlayViewCleanUpTask;
256 private boolean mOverlayViewEnabled;
257 private IBinder mWindowToken;
258 private Rect mOverlayFrame;
259 private long mStartPositionMs;
260 private long mCurrentPositionMs;
261 private final TimeShiftPositionTrackingRunnable
262 mTimeShiftPositionTrackingRunnable = new TimeShiftPositionTrackingRunnable();
264 private final Object mLock = new Object();
265 // @GuardedBy("mLock")
266 private ITvInputSessionCallback mSessionCallback;
267 // @GuardedBy("mLock")
268 private final List<Runnable> mPendingActions = new ArrayList<>();
271 * Creates a new Session.
273 * @param context The context of the application
275 public Session(Context context) {
277 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
278 mHandler = new Handler(context.getMainLooper());
279 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
283 * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
284 * called explicitly after the session is created to enable the overlay view.
286 * @param enable {@code true} if you want to enable the overlay view. {@code false}
289 public void setOverlayViewEnabled(final boolean enable) {
290 mHandler.post(new Runnable() {
293 if (enable == mOverlayViewEnabled) {
296 mOverlayViewEnabled = enable;
298 if (mWindowToken != null) {
299 createOverlayView(mWindowToken, mOverlayFrame);
302 removeOverlayView(false);
309 * Dispatches an event to the application using this session.
311 * @param eventType The type of the event.
312 * @param eventArgs Optional arguments of the event.
316 public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
317 if (eventType == null) {
318 throw new IllegalArgumentException("eventType cannot be null");
320 executeOrPostRunnable(new Runnable() {
324 if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
325 if (mSessionCallback != null) {
326 mSessionCallback.onSessionEvent(eventType, eventArgs);
328 } catch (RemoteException e) {
329 Log.w(TAG, "error in sending event (event=" + eventType + ")", e);
336 * Notifies the channel of the session is retuned by TV input.
338 * @param channelUri The URI of a channel.
340 public void notifyChannelRetuned(final Uri channelUri) {
341 executeOrPostRunnable(new Runnable() {
345 if (DEBUG) Log.d(TAG, "notifyChannelRetuned");
346 if (mSessionCallback != null) {
347 mSessionCallback.onChannelRetuned(channelUri);
349 } catch (RemoteException e) {
350 Log.w(TAG, "error in notifyChannelRetuned", e);
357 * Sends the list of all audio/video/subtitle tracks. The is used by the framework to
358 * maintain the track information for a given session, which in turn is used by
359 * {@link TvView#getTracks} for the application to retrieve metadata for a given track type.
360 * The TV input service must call this method as soon as the track information becomes
361 * available or is updated. Note that in a case where a part of the information for a
362 * certain track is updated, it is not necessary to create a new {@link TvTrackInfo} object
363 * with a different track ID.
365 * @param tracks A list which includes track information.
366 * @throws IllegalArgumentException if {@code tracks} contains redundant tracks.
368 public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
369 Set<String> trackIdSet = new HashSet<String>();
370 for (TvTrackInfo track : tracks) {
371 String trackId = track.getId();
372 if (trackIdSet.contains(trackId)) {
373 throw new IllegalArgumentException("redundant track ID: " + trackId);
375 trackIdSet.add(trackId);
379 // TODO: Validate the track list.
380 executeOrPostRunnable(new Runnable() {
384 if (DEBUG) Log.d(TAG, "notifyTracksChanged");
385 if (mSessionCallback != null) {
386 mSessionCallback.onTracksChanged(tracks);
388 } catch (RemoteException e) {
389 Log.w(TAG, "error in notifyTracksChanged", e);
396 * Sends the type and ID of a selected track. This is used to inform the application that a
397 * specific track is selected. The TV input service must call this method as soon as a track
398 * is selected either by default or in response to a call to {@link #onSelectTrack}. The
399 * selected track ID for a given type is maintained in the framework until the next call to
400 * this method even after the entire track list is updated (but is reset when the session is
401 * tuned to a new channel), so care must be taken not to result in an obsolete track ID.
403 * @param type The type of the selected track. The type can be
404 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
405 * {@link TvTrackInfo#TYPE_SUBTITLE}.
406 * @param trackId The ID of the selected track.
407 * @see #onSelectTrack
409 public void notifyTrackSelected(final int type, final String trackId) {
410 executeOrPostRunnable(new Runnable() {
414 if (DEBUG) Log.d(TAG, "notifyTrackSelected");
415 if (mSessionCallback != null) {
416 mSessionCallback.onTrackSelected(type, trackId);
418 } catch (RemoteException e) {
419 Log.w(TAG, "error in notifyTrackSelected", e);
426 * Informs the application that the video is now available for watching. This is primarily
427 * used to signal the application to unblock the screen. The TV input service must call this
428 * method as soon as the content rendered onto its surface gets ready for viewing.
430 * @see #notifyVideoUnavailable
432 public void notifyVideoAvailable() {
433 executeOrPostRunnable(new Runnable() {
437 if (DEBUG) Log.d(TAG, "notifyVideoAvailable");
438 if (mSessionCallback != null) {
439 mSessionCallback.onVideoAvailable();
441 } catch (RemoteException e) {
442 Log.w(TAG, "error in notifyVideoAvailable", e);
449 * Informs the application that the video became unavailable for some reason. This is
450 * primarily used to signal the application to block the screen not to show any intermittent
453 * @param reason The reason why the video became unavailable:
455 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
456 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
457 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
458 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
459 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
461 * @see #notifyVideoAvailable
463 public void notifyVideoUnavailable(final int reason) {
464 if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
465 || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
466 throw new IllegalArgumentException("Unknown reason: " + reason);
468 executeOrPostRunnable(new Runnable() {
472 if (DEBUG) Log.d(TAG, "notifyVideoUnavailable");
473 if (mSessionCallback != null) {
474 mSessionCallback.onVideoUnavailable(reason);
476 } catch (RemoteException e) {
477 Log.w(TAG, "error in notifyVideoUnavailable", e);
484 * Informs the application that the user is allowed to watch the current program content.
486 * <p>Each TV input service is required to query the system whether the user is allowed to
487 * watch the current program before showing it to the user if the parental controls is
488 * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
489 * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
490 * service should block the content or not is determined by invoking
491 * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
492 * with the content rating for the current program. Then the {@link TvInputManager} makes a
493 * judgment based on the user blocked ratings stored in the secure settings and returns the
494 * result. If the rating in question turns out to be allowed by the user, the TV input
495 * service must call this method to notify the application that is permitted to show the
498 * <p>Each TV input service also needs to continuously listen to any changes made to the
499 * parental controls settings by registering a broadcast receiver to receive
500 * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
501 * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
502 * reevaluate the current program with the new parental controls settings.
504 * @see #notifyContentBlocked
505 * @see TvInputManager
507 public void notifyContentAllowed() {
508 executeOrPostRunnable(new Runnable() {
512 if (DEBUG) Log.d(TAG, "notifyContentAllowed");
513 if (mSessionCallback != null) {
514 mSessionCallback.onContentAllowed();
516 } catch (RemoteException e) {
517 Log.w(TAG, "error in notifyContentAllowed", e);
524 * Informs the application that the current program content is blocked by parent controls.
526 * <p>Each TV input service is required to query the system whether the user is allowed to
527 * watch the current program before showing it to the user if the parental controls is
528 * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
529 * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
530 * service should block the content or not is determined by invoking
531 * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
532 * with the content rating for the current program. Then the {@link TvInputManager} makes a
533 * judgment based on the user blocked ratings stored in the secure settings and returns the
534 * result. If the rating in question turns out to be blocked, the TV input service must
535 * immediately block the content and call this method with the content rating of the current
536 * program to prompt the PIN verification screen.
538 * <p>Each TV input service also needs to continuously listen to any changes made to the
539 * parental controls settings by registering a broadcast receiver to receive
540 * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
541 * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
542 * reevaluate the current program with the new parental controls settings.
544 * @param rating The content rating for the current TV program.
545 * @see #notifyContentAllowed
546 * @see TvInputManager
548 public void notifyContentBlocked(@NonNull final TvContentRating rating) {
549 if (rating == null) {
550 throw new IllegalArgumentException("rating cannot be null");
552 executeOrPostRunnable(new Runnable() {
556 if (DEBUG) Log.d(TAG, "notifyContentBlocked");
557 if (mSessionCallback != null) {
558 mSessionCallback.onContentBlocked(rating.flattenToString());
560 } catch (RemoteException e) {
561 Log.w(TAG, "error in notifyContentBlocked", e);
568 * Informs the application that the time shift status is changed.
570 * <p>Prior to calling this method, the application assumes the status
571 * {@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}. Right after the session is created, it
572 * is important to invoke the method with the status
573 * {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} if the implementation does support
574 * time shifting, or {@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} otherwise. Failure
575 * to notifying the current status change immediately might result in an undesirable
576 * behavior in the application such as hiding the play controls.
578 * <p>If the status {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} is reported, the
579 * application assumes it can pause/resume playback, seek to a specified time position and
580 * set playback rate and audio mode. The implementation should override
581 * {@link #onTimeShiftPause}, {@link #onTimeShiftResume}, {@link #onTimeShiftSeekTo},
582 * {@link #onTimeShiftGetStartPosition}, {@link #onTimeShiftGetCurrentPosition} and
583 * {@link #onTimeShiftSetPlaybackRate}.
585 * @param status The current time shift status. Should be one of the followings.
587 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED}
588 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE}
589 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE}
592 public void notifyTimeShiftStatusChanged(final int status) {
593 executeOrPostRunnable(new Runnable() {
597 if (DEBUG) Log.d(TAG, "notifyTimeShiftStatusChanged");
598 if (mSessionCallback != null) {
599 mSessionCallback.onTimeShiftStatusChanged(status);
601 } catch (RemoteException e) {
602 Log.w(TAG, "error in notifyTimeShiftStatusChanged", e);
608 private void notifyTimeShiftStartPositionChanged(final long timeMs) {
609 executeOrPostRunnable(new Runnable() {
613 if (DEBUG) Log.d(TAG, "notifyTimeShiftStartPositionChanged");
614 if (mSessionCallback != null) {
615 mSessionCallback.onTimeShiftStartPositionChanged(timeMs);
617 } catch (RemoteException e) {
618 Log.w(TAG, "error in notifyTimeShiftStartPositionChanged", e);
624 private void notifyTimeShiftCurrentPositionChanged(final long timeMs) {
625 executeOrPostRunnable(new Runnable() {
629 if (DEBUG) Log.d(TAG, "notifyTimeShiftCurrentPositionChanged");
630 if (mSessionCallback != null) {
631 mSessionCallback.onTimeShiftCurrentPositionChanged(timeMs);
633 } catch (RemoteException e) {
634 Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged", e);
641 * Assigns a position of the {@link Surface} passed by {@link #onSetSurface}. The position
642 * is relative to an overlay view.
644 * @param left Left position in pixels, relative to the overlay view.
645 * @param top Top position in pixels, relative to the overlay view.
646 * @param right Right position in pixels, relative to the overlay view.
647 * @param bottom Bottom position in pixels, relative to the overlay view.
648 * @see #onOverlayViewSizeChanged
652 public void layoutSurface(final int left, final int top, final int right,
654 if (left > right || top > bottom) {
655 throw new IllegalArgumentException("Invalid parameter");
657 executeOrPostRunnable(new Runnable() {
661 if (DEBUG) Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top + ", r="
662 + right + ", b=" + bottom + ",)");
663 if (mSessionCallback != null) {
664 mSessionCallback.onLayoutSurface(left, top, right, bottom);
666 } catch (RemoteException e) {
667 Log.w(TAG, "error in layoutSurface", e);
674 * Called when the session is released.
676 public abstract void onRelease();
679 * Sets the current session as the main session. The main session is a session whose
680 * corresponding TV input determines the HDMI-CEC active source device.
682 * <p>TV input service that manages HDMI-CEC logical device should implement {@link
683 * #onSetMain} to (1) select the corresponding HDMI logical device as the source device
684 * when {@code isMain} is {@code true}, and to (2) select the internal device (= TV itself)
685 * as the source device when {@code isMain} is {@code false} and the session is still main.
686 * Also, if a surface is passed to a non-main session and active source is changed to
687 * initiate the surface, the active source should be returned to the main session.
689 * <p>{@link TvView} guarantees that, when tuning involves a session transition, {@code
690 * onSetMain(true)} for new session is called first, {@code onSetMain(false)} for old
691 * session is called afterwards. This allows {@code onSetMain(false)} to be no-op when TV
692 * input service knows that the next main session corresponds to another HDMI logical
693 * device. Practically, this implies that one TV input service should handle all HDMI port
694 * and HDMI-CEC logical devices for smooth active source transition.
696 * @param isMain If true, session should become main.
697 * @see TvView#setMain
701 public void onSetMain(boolean isMain) {
705 * Sets the {@link Surface} for the current input session on which the TV input renders
708 * <p>When {@code setSurface(null)} is called, the implementation should stop using the
709 * Surface object previously given and release any references to it.
711 * @param surface possibly {@code null} {@link Surface} the application passes to this TV
713 * @return {@code true} if the surface was set, {@code false} otherwise.
715 public abstract boolean onSetSurface(@Nullable Surface surface);
718 * Called after any structural changes (format or size) have been made to the
719 * {@link Surface} passed by {@link #onSetSurface}. This method is always called
720 * at least once, after {@link #onSetSurface} with non-null {@link Surface} is called.
722 * @param format The new PixelFormat of the {@link Surface}.
723 * @param width The new width of the {@link Surface}.
724 * @param height The new height of the {@link Surface}.
726 public void onSurfaceChanged(int format, int width, int height) {
730 * Called when a size of an overlay view is changed by the application. Even when the
731 * overlay view is disabled by {@link #setOverlayViewEnabled}, this is called. The size is
732 * same as the size of {@link Surface} in general. Once {@link #layoutSurface} is called,
733 * the sizes of {@link Surface} and the overlay view can be different.
735 * @param width The width of the overlay view.
736 * @param height The height of the overlay view.
740 public void onOverlayViewSizeChanged(int width, int height) {
744 * Sets the relative stream volume of the current TV input session.
746 * <p>The implementation should honor this request in order to handle audio focus changes or
747 * mute the current session when multiple sessions, possibly from different inputs are
748 * active. If the method has not yet been called, the implementation should assume the
749 * default value of {@code 1.0f}.
751 * @param volume A volume value between {@code 0.0f} to {@code 1.0f}.
753 public abstract void onSetStreamVolume(float volume);
756 * Tunes to a given channel. When the video is available, {@link #notifyVideoAvailable()}
757 * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the TV
758 * input cannot continue playing the given channel.
760 * @param channelUri The URI of the channel.
761 * @return {@code true} if the tuning was successful, {@code false} otherwise.
763 public abstract boolean onTune(Uri channelUri);
766 * Calls {@link #onTune(Uri)}. Override this method in order to handle {@code params}.
768 * @param channelUri The URI of the channel.
769 * @param params The extra parameters from other applications.
770 * @return {@code true} if the tuning was successful, {@code false} otherwise.
774 public boolean onTune(Uri channelUri, Bundle params) {
775 return onTune(channelUri);
779 * Enables or disables the caption.
781 * <p>The locale for the user's preferred captioning language can be obtained by calling
782 * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
784 * @param enabled {@code true} to enable, {@code false} to disable.
785 * @see CaptioningManager
787 public abstract void onSetCaptionEnabled(boolean enabled);
790 * Requests to unblock the content according to the given rating.
792 * <p>The implementation should unblock the content.
793 * TV input service has responsibility to decide when/how the unblock expires
794 * while it can keep previously unblocked ratings in order not to ask a user
795 * to unblock whenever a content rating is changed.
796 * Therefore an unblocked rating can be valid for a channel, a program,
797 * or certain amount of time depending on the implementation.
799 * @param unblockedRating An unblocked content rating
801 public void onUnblockContent(TvContentRating unblockedRating) {
805 * Selects a given track.
807 * <p>If this is done successfully, the implementation should call
808 * {@link #notifyTrackSelected} to help applications maintain the up-to-date list of the
811 * @param trackId The ID of the track to select. {@code null} means to unselect the current
812 * track for a given type.
813 * @param type The type of the track to select. The type can be
814 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
815 * {@link TvTrackInfo#TYPE_SUBTITLE}.
816 * @return {@code true} if the track selection was successful, {@code false} otherwise.
817 * @see #notifyTrackSelected
819 public boolean onSelectTrack(int type, @Nullable String trackId) {
824 * Processes a private command sent from the application to the TV input. This can be used
825 * to provide domain-specific features that are only known between certain TV inputs and
828 * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
829 * i.e. prefixed with a package name you own, so that different developers will
830 * not create conflicting commands.
831 * @param data Any data to include with the command.
835 public void onAppPrivateCommand(@NonNull String action, Bundle data) {
839 * Called when the application requests to create an overlay view. Each session
840 * implementation can override this method and return its own view.
842 * @return a view attached to the overlay window
844 public View onCreateOverlayView() {
849 * Called when the application requests to pause playback.
851 * @see #onTimeShiftResume
852 * @see #onTimeShiftSeekTo
853 * @see #onTimeShiftSetPlaybackRate
854 * @see #onTimeShiftGetStartPosition
855 * @see #onTimeShiftGetCurrentPosition
857 public void onTimeShiftPause() {
861 * Called when the application requests to resume playback.
863 * @see #onTimeShiftPause
864 * @see #onTimeShiftSeekTo
865 * @see #onTimeShiftSetPlaybackRate
866 * @see #onTimeShiftGetStartPosition
867 * @see #onTimeShiftGetCurrentPosition
869 public void onTimeShiftResume() {
873 * Called when the application requests to seek to a specified time position. Normally, the
874 * position is given within range between the start and the current time, inclusively. The
875 * implementation is expected to seek to the nearest time position if the given position is
878 * @param timeMs The time position to seek to, in milliseconds since the epoch.
879 * @see #onTimeShiftResume
880 * @see #onTimeShiftPause
881 * @see #onTimeShiftSetPlaybackRate
882 * @see #onTimeShiftGetStartPosition
883 * @see #onTimeShiftGetCurrentPosition
885 public void onTimeShiftSeekTo(long timeMs) {
889 * Called when the application sets playback rate and audio mode.
891 * <p>Once a playback rate is set, the implementation should honor the value until a new
892 * tune request. Pause/resume/seek request does not reset the playback rate previously set.
894 * @param rate The ratio between desired playback rate and normal one.
895 * @param audioMode Audio playback mode. Must be one of the supported audio modes:
897 * <li> {@link android.media.MediaPlayer#PLAYBACK_RATE_AUDIO_MODE_RESAMPLE}
899 * @see #onTimeShiftResume
900 * @see #onTimeShiftPause
901 * @see #onTimeShiftSeekTo
902 * @see #onTimeShiftGetStartPosition
903 * @see #onTimeShiftGetCurrentPosition
905 public void onTimeShiftSetPlaybackRate(float rate, int audioMode) {
909 * Returns the start playback position for time shifting, in milliseconds since the epoch.
910 * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
913 * <p>The start playback position of the time shifted program should be adjusted when the
914 * implementation cannot retain the whole recorded program due to some reason (e.g.
915 * limitation on storage space). It is the earliest possible time position that the user can
916 * seek to, thus failure to notifying its change immediately might result in bad experience
917 * where the application allows the user to seek to an invalid time position.
919 * @see #onTimeShiftResume
920 * @see #onTimeShiftPause
921 * @see #onTimeShiftSeekTo
922 * @see #onTimeShiftSetPlaybackRate
923 * @see #onTimeShiftGetCurrentPosition
925 public long onTimeShiftGetStartPosition() {
926 return TvInputManager.TIME_SHIFT_INVALID_TIME;
930 * Returns the current playback position for time shifting, in milliseconds since the epoch.
931 * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the
934 * @see #onTimeShiftResume
935 * @see #onTimeShiftPause
936 * @see #onTimeShiftSeekTo
937 * @see #onTimeShiftSetPlaybackRate
938 * @see #onTimeShiftGetStartPosition
940 public long onTimeShiftGetCurrentPosition() {
941 return TvInputManager.TIME_SHIFT_INVALID_TIME;
945 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
946 * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
948 * <p>Override this to intercept key down events before they are processed by the
949 * application. If you return true, the application will not process the event itself. If
950 * you return false, the normal application processing will occur as if the TV input had not
951 * seen the event at all.
953 * @param keyCode The value in event.getKeyCode().
954 * @param event Description of the key event.
955 * @return If you handled the event, return {@code true}. If you want to allow the event to
956 * be handled by the next receiver, return {@code false}.
959 public boolean onKeyDown(int keyCode, KeyEvent event) {
964 * Default implementation of
965 * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
966 * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
968 * <p>Override this to intercept key long press events before they are processed by the
969 * application. If you return true, the application will not process the event itself. If
970 * you return false, the normal application processing will occur as if the TV input had not
971 * seen the event at all.
973 * @param keyCode The value in event.getKeyCode().
974 * @param event Description of the key event.
975 * @return If you handled the event, return {@code true}. If you want to allow the event to
976 * be handled by the next receiver, return {@code false}.
979 public boolean onKeyLongPress(int keyCode, KeyEvent event) {
984 * Default implementation of
985 * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
986 * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
988 * <p>Override this to intercept special key multiple events before they are processed by
989 * the application. If you return true, the application will not itself process the event.
990 * If you return false, the normal application processing will occur as if the TV input had
991 * not seen the event at all.
993 * @param keyCode The value in event.getKeyCode().
994 * @param count The number of times the action was made.
995 * @param event Description of the key event.
996 * @return If you handled the event, return {@code true}. If you want to allow the event to
997 * be handled by the next receiver, return {@code false}.
1000 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
1005 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
1006 * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
1008 * <p>Override this to intercept key up events before they are processed by the application.
1009 * If you return true, the application will not itself process the event. If you return false,
1010 * the normal application processing will occur as if the TV input had not seen the event at
1013 * @param keyCode The value in event.getKeyCode().
1014 * @param event Description of the key event.
1015 * @return If you handled the event, return {@code true}. If you want to allow the event to
1016 * be handled by the next receiver, return {@code false}.
1019 public boolean onKeyUp(int keyCode, KeyEvent event) {
1024 * Implement this method to handle touch screen motion events on the current input session.
1026 * @param event The motion event being received.
1027 * @return If you handled the event, return {@code true}. If you want to allow the event to
1028 * be handled by the next receiver, return {@code false}.
1029 * @see View#onTouchEvent
1031 public boolean onTouchEvent(MotionEvent event) {
1036 * Implement this method to handle trackball events on the current input session.
1038 * @param event The motion event being received.
1039 * @return If you handled the event, return {@code true}. If you want to allow the event to
1040 * be handled by the next receiver, return {@code false}.
1041 * @see View#onTrackballEvent
1043 public boolean onTrackballEvent(MotionEvent event) {
1048 * Implement this method to handle generic motion events on the current input session.
1050 * @param event The motion event being received.
1051 * @return If you handled the event, return {@code true}. If you want to allow the event to
1052 * be handled by the next receiver, return {@code false}.
1053 * @see View#onGenericMotionEvent
1055 public boolean onGenericMotionEvent(MotionEvent event) {
1060 * This method is called when the application would like to stop using the current input
1065 if (mSurface != null) {
1069 synchronized(mLock) {
1070 mSessionCallback = null;
1071 mPendingActions.clear();
1073 // Removes the overlay view lastly so that any hanging on the main thread can be handled
1074 // in {@link #scheduleOverlayViewCleanup}.
1075 removeOverlayView(true);
1076 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
1080 * Calls {@link #onSetMain}.
1082 void setMain(boolean isMain) {
1087 * Calls {@link #onSetSurface}.
1089 void setSurface(Surface surface) {
1090 onSetSurface(surface);
1091 if (mSurface != null) {
1095 // TODO: Handle failure.
1099 * Calls {@link #onSurfaceChanged}.
1101 void dispatchSurfaceChanged(int format, int width, int height) {
1103 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
1104 + ", height=" + height + ")");
1106 onSurfaceChanged(format, width, height);
1110 * Calls {@link #onSetStreamVolume}.
1112 void setStreamVolume(float volume) {
1113 onSetStreamVolume(volume);
1117 * Calls {@link #onTune}.
1119 void tune(Uri channelUri, Bundle params) {
1120 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
1121 onTune(channelUri, params);
1122 // TODO: Handle failure.
1126 * Calls {@link #onSetCaptionEnabled}.
1128 void setCaptionEnabled(boolean enabled) {
1129 onSetCaptionEnabled(enabled);
1133 * Calls {@link #onSelectTrack}.
1135 void selectTrack(int type, String trackId) {
1136 onSelectTrack(type, trackId);
1140 * Calls {@link #onUnblockContent}.
1142 void unblockContent(String unblockedRating) {
1143 onUnblockContent(TvContentRating.unflattenFromString(unblockedRating));
1144 // TODO: Handle failure.
1148 * Calls {@link #onAppPrivateCommand}.
1150 void appPrivateCommand(String action, Bundle data) {
1151 onAppPrivateCommand(action, data);
1155 * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
1156 * to the overlay window.
1158 * @param windowToken A window token of the application.
1159 * @param frame A position of the overlay view.
1161 void createOverlayView(IBinder windowToken, Rect frame) {
1162 if (mOverlayViewContainer != null) {
1163 removeOverlayView(false);
1165 if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
1166 mWindowToken = windowToken;
1167 mOverlayFrame = frame;
1168 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
1169 if (!mOverlayViewEnabled) {
1172 mOverlayView = onCreateOverlayView();
1173 if (mOverlayView == null) {
1176 if (mOverlayViewCleanUpTask != null) {
1177 mOverlayViewCleanUpTask.cancel(true);
1178 mOverlayViewCleanUpTask = null;
1180 // Creates a container view to check hanging on the overlay view detaching.
1181 // Adding/removing the overlay view to/from the container make the view attach/detach
1182 // logic run on the main thread.
1183 mOverlayViewContainer = new FrameLayout(mContext);
1184 mOverlayViewContainer.addView(mOverlayView);
1185 // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
1186 // an overlay window above the media window but below the application window.
1187 int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
1188 // We make the overlay view non-focusable and non-touchable so that
1189 // the application that owns the window token can decide whether to consume or
1190 // dispatch the input events.
1191 int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1192 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
1193 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1194 mWindowParams = new WindowManager.LayoutParams(
1195 frame.right - frame.left, frame.bottom - frame.top,
1196 frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
1197 mWindowParams.privateFlags |=
1198 WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
1199 mWindowParams.gravity = Gravity.START | Gravity.TOP;
1200 mWindowParams.token = windowToken;
1201 mWindowManager.addView(mOverlayViewContainer, mWindowParams);
1205 * Relayouts the current overlay view.
1207 * @param frame A new position of the overlay view.
1209 void relayoutOverlayView(Rect frame) {
1210 if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
1211 if (mOverlayFrame == null || mOverlayFrame.width() != frame.width()
1212 || mOverlayFrame.height() != frame.height()) {
1213 // Note: relayoutOverlayView is called whenever TvView's layout is changed
1214 // regardless of setOverlayViewEnabled.
1215 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
1217 mOverlayFrame = frame;
1218 if (!mOverlayViewEnabled || mOverlayViewContainer == null) {
1221 mWindowParams.x = frame.left;
1222 mWindowParams.y = frame.top;
1223 mWindowParams.width = frame.right - frame.left;
1224 mWindowParams.height = frame.bottom - frame.top;
1225 mWindowManager.updateViewLayout(mOverlayViewContainer, mWindowParams);
1229 * Removes the current overlay view.
1231 void removeOverlayView(boolean clearWindowToken) {
1232 if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayViewContainer + ")");
1233 if (clearWindowToken) {
1234 mWindowToken = null;
1235 mOverlayFrame = null;
1237 if (mOverlayViewContainer != null) {
1238 // Removes the overlay view from the view hierarchy in advance so that it can be
1239 // cleaned up in the {@link OverlayViewCleanUpTask} if the remove process is
1241 mOverlayViewContainer.removeView(mOverlayView);
1242 mOverlayView = null;
1243 mWindowManager.removeView(mOverlayViewContainer);
1244 mOverlayViewContainer = null;
1245 mWindowParams = null;
1250 * Calls {@link #onTimeShiftPause}.
1252 void timeShiftPause() {
1257 * Calls {@link #onTimeShiftResume}.
1259 void timeShiftResume() {
1260 onTimeShiftResume();
1264 * Calls {@link #onTimeShiftSeekTo}.
1266 void timeShiftSeekTo(long timeMs) {
1267 onTimeShiftSeekTo(timeMs);
1271 * Calls {@link #onTimeShiftSetPlaybackRate}.
1273 void timeShiftSetPlaybackRate(float rate, int audioMode) {
1274 onTimeShiftSetPlaybackRate(rate, audioMode);
1278 * Enable/disable position tracking.
1280 * @param enable {@code true} to enable tracking, {@code false} otherwise.
1282 void timeShiftEnablePositionTracking(boolean enable) {
1284 mHandler.post(mTimeShiftPositionTrackingRunnable);
1286 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
1287 mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
1288 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
1293 * Schedules a task which checks whether the overlay view is detached and kills the process
1294 * if it is not. Note that this method is expected to be called in a non-main thread.
1296 void scheduleOverlayViewCleanup() {
1297 View overlayViewParent = mOverlayViewContainer;
1298 if (overlayViewParent != null) {
1299 mOverlayViewCleanUpTask = new OverlayViewCleanUpTask();
1300 mOverlayViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
1306 * Takes care of dispatching incoming input events and tells whether the event was handled.
1308 int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
1309 if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
1310 boolean isNavigationKey = false;
1311 boolean skipDispatchToOverlayView = false;
1312 if (event instanceof KeyEvent) {
1313 KeyEvent keyEvent = (KeyEvent) event;
1314 if (keyEvent.dispatch(this, mDispatcherState, this)) {
1315 return TvInputManager.Session.DISPATCH_HANDLED;
1317 isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
1318 // When media keys and KEYCODE_MEDIA_AUDIO_TRACK are dispatched to ViewRootImpl,
1319 // ViewRootImpl always consumes the keys. In this case, the application loses
1320 // a chance to handle media keys. Therefore, media keys are not dispatched to
1322 skipDispatchToOverlayView = KeyEvent.isMediaKey(keyEvent.getKeyCode())
1323 || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK;
1324 } else if (event instanceof MotionEvent) {
1325 MotionEvent motionEvent = (MotionEvent) event;
1326 final int source = motionEvent.getSource();
1327 if (motionEvent.isTouchEvent()) {
1328 if (onTouchEvent(motionEvent)) {
1329 return TvInputManager.Session.DISPATCH_HANDLED;
1331 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
1332 if (onTrackballEvent(motionEvent)) {
1333 return TvInputManager.Session.DISPATCH_HANDLED;
1336 if (onGenericMotionEvent(motionEvent)) {
1337 return TvInputManager.Session.DISPATCH_HANDLED;
1341 if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow()
1342 || skipDispatchToOverlayView) {
1343 return TvInputManager.Session.DISPATCH_NOT_HANDLED;
1345 if (!mOverlayViewContainer.hasWindowFocus()) {
1346 mOverlayViewContainer.getViewRootImpl().windowFocusChanged(true, true);
1348 if (isNavigationKey && mOverlayViewContainer.hasFocusable()) {
1349 // If mOverlayView has focusable views, navigation key events should be always
1350 // handled. If not, it can make the application UI navigation messed up.
1351 // For example, in the case that the left-most view is focused, a left key event
1352 // will not be handled in ViewRootImpl. Then, the left key event will be handled in
1353 // the application during the UI navigation of the TV input.
1354 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event);
1355 return TvInputManager.Session.DISPATCH_HANDLED;
1357 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event, receiver);
1358 return TvInputManager.Session.DISPATCH_IN_PROGRESS;
1362 private void initialize(ITvInputSessionCallback callback) {
1363 synchronized(mLock) {
1364 mSessionCallback = callback;
1365 for (Runnable runnable : mPendingActions) {
1368 mPendingActions.clear();
1372 private final void executeOrPostRunnable(Runnable action) {
1373 synchronized(mLock) {
1374 if (mSessionCallback == null) {
1375 // The session is not initialized yet.
1376 mPendingActions.add(action);
1378 if (mHandler.getLooper().isCurrentThread()) {
1381 // Posts the runnable if this is not called from the main thread
1382 mHandler.post(action);
1388 private final class TimeShiftPositionTrackingRunnable implements Runnable {
1391 long startPositionMs = onTimeShiftGetStartPosition();
1392 if (mStartPositionMs != startPositionMs) {
1393 mStartPositionMs = startPositionMs;
1394 notifyTimeShiftStartPositionChanged(startPositionMs);
1396 long currentPositionMs = onTimeShiftGetCurrentPosition();
1397 if (mCurrentPositionMs != currentPositionMs) {
1398 mCurrentPositionMs = currentPositionMs;
1399 notifyTimeShiftCurrentPositionChanged(currentPositionMs);
1401 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable);
1402 mHandler.postDelayed(mTimeShiftPositionTrackingRunnable,
1403 POSITION_UPDATE_INTERVAL_MS);
1407 private final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> {
1409 protected Void doInBackground(View... views) {
1410 View overlayViewParent = views[0];
1412 Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT_MS);
1413 } catch (InterruptedException e) {
1416 if (isCancelled()) {
1419 if (overlayViewParent.isAttachedToWindow()) {
1420 Log.e(TAG, "Time out on releasing overlay view. Killing "
1421 + overlayViewParent.getContext().getPackageName());
1422 Process.killProcess(Process.myPid());
1430 * Base class for a TV input session which represents an external device connected to a
1431 * hardware TV input.
1433 * <p>This class is for an input which provides channels for the external set-top box to the
1434 * application. Once a TV input returns an implementation of this class on
1435 * {@link #onCreateSession(String)}, the framework will create a separate session for
1436 * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
1437 * that the user can see the screen of the hardware TV Input when she tunes to a channel from
1438 * this TV input. The implementation of this class is expected to change the channel of the
1439 * external set-top box via a proprietary protocol when {@link HardwareSession#onTune(Uri)} is
1440 * requested by the application.
1442 * <p>Note that this class is not for inputs for internal hardware like built-in tuner and HDMI
1445 * @see #onCreateSession(String)
1447 public abstract static class HardwareSession extends Session {
1450 * Creates a new HardwareSession.
1452 * @param context The context of the application
1454 public HardwareSession(Context context) {
1458 private TvInputManager.Session mHardwareSession;
1459 private ITvInputSession mProxySession;
1460 private ITvInputSessionCallback mProxySessionCallback;
1461 private Handler mServiceHandler;
1464 * Returns the hardware TV input ID the external device is connected to.
1466 * <p>TV input is expected to provide {@link android.R.attr#setupActivity} so that
1467 * the application can launch it before using this TV input. The setup activity may let
1468 * the user select the hardware TV input to which the external device is connected. The ID
1469 * of the selected one should be stored in the TV input so that it can be returned here.
1471 public abstract String getHardwareInputId();
1473 private final TvInputManager.SessionCallback mHardwareSessionCallback =
1474 new TvInputManager.SessionCallback() {
1476 public void onSessionCreated(TvInputManager.Session session) {
1477 mHardwareSession = session;
1478 SomeArgs args = SomeArgs.obtain();
1479 if (session != null) {
1480 args.arg1 = HardwareSession.this;
1481 args.arg2 = mProxySession;
1482 args.arg3 = mProxySessionCallback;
1483 args.arg4 = session.getToken();
1484 session.tune(TvContract.buildChannelUriForPassthroughInput(
1485 getHardwareInputId()));
1489 args.arg3 = mProxySessionCallback;
1493 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
1498 public void onVideoAvailable(final TvInputManager.Session session) {
1499 if (mHardwareSession == session) {
1500 onHardwareVideoAvailable();
1505 public void onVideoUnavailable(final TvInputManager.Session session,
1507 if (mHardwareSession == session) {
1508 onHardwareVideoUnavailable(reason);
1514 * This method will not be called in {@link HardwareSession}. Framework will
1515 * forward the application's surface to the hardware TV input.
1518 public final boolean onSetSurface(Surface surface) {
1519 Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
1524 * Called when the underlying hardware TV input session calls
1525 * {@link TvInputService.Session#notifyVideoAvailable()}.
1527 public void onHardwareVideoAvailable() { }
1530 * Called when the underlying hardware TV input session calls
1531 * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
1533 * @param reason The reason that the hardware TV input stopped the playback:
1535 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
1536 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
1537 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
1538 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
1539 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY}
1542 public void onHardwareVideoUnavailable(int reason) { }
1546 public static boolean isNavigationKey(int keyCode) {
1548 case KeyEvent.KEYCODE_DPAD_LEFT:
1549 case KeyEvent.KEYCODE_DPAD_RIGHT:
1550 case KeyEvent.KEYCODE_DPAD_UP:
1551 case KeyEvent.KEYCODE_DPAD_DOWN:
1552 case KeyEvent.KEYCODE_DPAD_CENTER:
1553 case KeyEvent.KEYCODE_PAGE_UP:
1554 case KeyEvent.KEYCODE_PAGE_DOWN:
1555 case KeyEvent.KEYCODE_MOVE_HOME:
1556 case KeyEvent.KEYCODE_MOVE_END:
1557 case KeyEvent.KEYCODE_TAB:
1558 case KeyEvent.KEYCODE_SPACE:
1559 case KeyEvent.KEYCODE_ENTER:
1565 @SuppressLint("HandlerLeak")
1566 private final class ServiceHandler extends Handler {
1567 private static final int DO_CREATE_SESSION = 1;
1568 private static final int DO_NOTIFY_SESSION_CREATED = 2;
1569 private static final int DO_ADD_HARDWARE_TV_INPUT = 3;
1570 private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4;
1571 private static final int DO_ADD_HDMI_TV_INPUT = 5;
1572 private static final int DO_REMOVE_HDMI_TV_INPUT = 6;
1574 private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1575 int n = mCallbacks.beginBroadcast();
1576 for (int i = 0; i < n; ++i) {
1578 mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo);
1579 } catch (RemoteException e) {
1580 Log.e(TAG, "error in broadcastAddHardwareTvInput", e);
1583 mCallbacks.finishBroadcast();
1586 private void broadcastAddHdmiTvInput(int id, TvInputInfo inputInfo) {
1587 int n = mCallbacks.beginBroadcast();
1588 for (int i = 0; i < n; ++i) {
1590 mCallbacks.getBroadcastItem(i).addHdmiTvInput(id, inputInfo);
1591 } catch (RemoteException e) {
1592 Log.e(TAG, "error in broadcastAddHdmiTvInput", e);
1595 mCallbacks.finishBroadcast();
1598 private void broadcastRemoveTvInput(String inputId) {
1599 int n = mCallbacks.beginBroadcast();
1600 for (int i = 0; i < n; ++i) {
1602 mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
1603 } catch (RemoteException e) {
1604 Log.e(TAG, "error in broadcastRemoveTvInput", e);
1607 mCallbacks.finishBroadcast();
1611 public final void handleMessage(Message msg) {
1613 case DO_CREATE_SESSION: {
1614 SomeArgs args = (SomeArgs) msg.obj;
1615 InputChannel channel = (InputChannel) args.arg1;
1616 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
1617 String inputId = (String) args.arg3;
1619 Session sessionImpl = onCreateSession(inputId);
1620 if (sessionImpl == null) {
1622 // Failed to create a session.
1623 cb.onSessionCreated(null, null);
1624 } catch (RemoteException e) {
1625 Log.e(TAG, "error in onSessionCreated", e);
1629 ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
1630 sessionImpl, channel);
1631 if (sessionImpl instanceof HardwareSession) {
1632 HardwareSession proxySession =
1633 ((HardwareSession) sessionImpl);
1634 String harewareInputId = proxySession.getHardwareInputId();
1635 if (TextUtils.isEmpty(harewareInputId) ||
1636 !isPassthroughInput(harewareInputId)) {
1637 if (TextUtils.isEmpty(harewareInputId)) {
1638 Log.w(TAG, "Hardware input id is not setup yet.");
1640 Log.w(TAG, "Invalid hardware input id : " + harewareInputId);
1642 sessionImpl.onRelease();
1644 cb.onSessionCreated(null, null);
1645 } catch (RemoteException e) {
1646 Log.e(TAG, "error in onSessionCreated", e);
1650 proxySession.mProxySession = stub;
1651 proxySession.mProxySessionCallback = cb;
1652 proxySession.mServiceHandler = mServiceHandler;
1653 TvInputManager manager = (TvInputManager) getSystemService(
1654 Context.TV_INPUT_SERVICE);
1655 manager.createSession(harewareInputId,
1656 proxySession.mHardwareSessionCallback, mServiceHandler);
1658 SomeArgs someArgs = SomeArgs.obtain();
1659 someArgs.arg1 = sessionImpl;
1660 someArgs.arg2 = stub;
1662 someArgs.arg4 = null;
1663 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
1664 someArgs).sendToTarget();
1668 case DO_NOTIFY_SESSION_CREATED: {
1669 SomeArgs args = (SomeArgs) msg.obj;
1670 Session sessionImpl = (Session) args.arg1;
1671 ITvInputSession stub = (ITvInputSession) args.arg2;
1672 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg3;
1673 IBinder hardwareSessionToken = (IBinder) args.arg4;
1675 cb.onSessionCreated(stub, hardwareSessionToken);
1676 } catch (RemoteException e) {
1677 Log.e(TAG, "error in onSessionCreated", e);
1679 if (sessionImpl != null) {
1680 sessionImpl.initialize(cb);
1685 case DO_ADD_HARDWARE_TV_INPUT: {
1686 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1687 TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
1688 if (inputInfo != null) {
1689 broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo);
1693 case DO_REMOVE_HARDWARE_TV_INPUT: {
1694 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1695 String inputId = onHardwareRemoved(hardwareInfo);
1696 if (inputId != null) {
1697 broadcastRemoveTvInput(inputId);
1701 case DO_ADD_HDMI_TV_INPUT: {
1702 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
1703 TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
1704 if (inputInfo != null) {
1705 broadcastAddHdmiTvInput(deviceInfo.getId(), inputInfo);
1709 case DO_REMOVE_HDMI_TV_INPUT: {
1710 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
1711 String inputId = onHdmiDeviceRemoved(deviceInfo);
1712 if (inputId != null) {
1713 broadcastRemoveTvInput(inputId);
1718 Log.w(TAG, "Unhandled message code: " + msg.what);