TvInputService.java revision 8ddb01ec7b1360e9fccf341b5f1989182160fcee
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media.tv;
18
19import android.annotation.SuppressLint;
20import android.annotation.SystemApi;
21import android.app.Service;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.PixelFormat;
25import android.graphics.Rect;
26import android.hardware.hdmi.HdmiDeviceInfo;
27import android.net.Uri;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Message;
32import android.os.RemoteCallbackList;
33import android.os.RemoteException;
34import android.text.TextUtils;
35import android.util.Log;
36import android.view.Gravity;
37import android.view.InputChannel;
38import android.view.InputDevice;
39import android.view.InputEvent;
40import android.view.InputEventReceiver;
41import android.view.KeyEvent;
42import android.view.MotionEvent;
43import android.view.Surface;
44import android.view.View;
45import android.view.WindowManager;
46import android.view.accessibility.CaptioningManager;
47
48import com.android.internal.annotations.VisibleForTesting;
49import com.android.internal.os.SomeArgs;
50
51import java.util.HashSet;
52import java.util.List;
53import java.util.Set;
54
55/**
56 * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which
57 * provides pass-through video or broadcast TV programs.
58 * <p>
59 * Applications will not normally use this service themselves, instead relying on the standard
60 * interaction provided by {@link TvView}. Those implementing TV input services should normally do
61 * so by deriving from this class and providing their own session implementation based on
62 * {@link TvInputService.Session}. All TV input services must require that clients hold the
63 * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this
64 * permission is not specified in the manifest, the system will refuse to bind to that TV input
65 * service.
66 * </p>
67 */
68public abstract class TvInputService extends Service {
69    private static final boolean DEBUG = false;
70    private static final String TAG = "TvInputService";
71
72    /**
73     * This is the interface name that a service implementing a TV input should say that it support
74     * -- that is, this is the action it uses for its intent filter. To be supported, the service
75     * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that
76     * other applications cannot abuse it.
77     */
78    public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
79
80    /**
81     * Name under which a TvInputService component publishes information about itself.
82     * This meta-data must reference an XML resource containing an
83     * <code>&lt;{@link android.R.styleable#TvInputService tv-input}&gt;</code>
84     * tag.
85     */
86    public static final String SERVICE_META_DATA = "android.media.tv.input";
87
88    /**
89     * Handler instance to handle request from TV Input Manager Service. Should be run in the main
90     * looper to be synchronously run with {@code Session.mHandler}.
91     */
92    private final Handler mServiceHandler = new ServiceHandler();
93    private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks =
94            new RemoteCallbackList<ITvInputServiceCallback>();
95
96    private TvInputManager mTvInputManager;
97
98    @Override
99    public final IBinder onBind(Intent intent) {
100        return new ITvInputService.Stub() {
101            @Override
102            public void registerCallback(ITvInputServiceCallback cb) {
103                if (cb != null) {
104                    mCallbacks.register(cb);
105                }
106            }
107
108            @Override
109            public void unregisterCallback(ITvInputServiceCallback cb) {
110                if (cb != null) {
111                    mCallbacks.unregister(cb);
112                }
113            }
114
115            @Override
116            public void createSession(InputChannel channel, ITvInputSessionCallback cb,
117                    String inputId) {
118                if (channel == null) {
119                    Log.w(TAG, "Creating session without input channel");
120                }
121                if (cb == null) {
122                    return;
123                }
124                SomeArgs args = SomeArgs.obtain();
125                args.arg1 = channel;
126                args.arg2 = cb;
127                args.arg3 = inputId;
128                mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
129            }
130
131            @Override
132            public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) {
133                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_TV_INPUT,
134                        hardwareInfo).sendToTarget();
135            }
136
137            @Override
138            public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
139                mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_TV_INPUT,
140                        hardwareInfo).sendToTarget();
141            }
142
143            @Override
144            public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
145                mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_TV_INPUT,
146                        deviceInfo).sendToTarget();
147            }
148
149            @Override
150            public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
151                mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_TV_INPUT,
152                        deviceInfo).sendToTarget();
153            }
154        };
155    }
156
157    /**
158     * Get the number of callbacks that are registered.
159     * @hide
160     */
161    @VisibleForTesting
162    public final int getRegisteredCallbackCount() {
163        return mCallbacks.getRegisteredCallbackCount();
164    }
165
166    /**
167     * Returns a concrete implementation of {@link Session}.
168     * <p>
169     * May return {@code null} if this TV input service fails to create a session for some reason.
170     * If TV input represents an external device connected to a hardware TV input,
171     * {@link HardwareSession} should be returned.
172     * </p>
173     * @param inputId The ID of the TV input associated with the session.
174     */
175    public abstract Session onCreateSession(String inputId);
176
177    /**
178     * Returns a new {@link TvInputInfo} object if this service is responsible for
179     * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
180     * ignoring all hardware input.
181     *
182     * @param hardwareInfo {@link TvInputHardwareInfo} object just added.
183     * @hide
184     */
185    @SystemApi
186    public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
187        return null;
188    }
189
190    /**
191     * Returns the input ID for {@code deviceId} if it is handled by this service;
192     * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware
193     * input.
194     *
195     * @param hardwareInfo {@link TvInputHardwareInfo} object just removed.
196     * @hide
197     */
198    @SystemApi
199    public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
200        return null;
201    }
202
203    /**
204     * Returns a new {@link TvInputInfo} object if this service is responsible for
205     * {@code deviceInfo}; otherwise, return {@code null}. Override to modify default behavior of
206     * ignoring all HDMI logical input device.
207     *
208     * @param deviceInfo {@link HdmiDeviceInfo} object just added.
209     * @hide
210     */
211    @SystemApi
212    public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) {
213        return null;
214    }
215
216    /**
217     * Returns the input ID for {@code deviceInfo} if it is handled by this service; otherwise,
218     * return {@code null}. Override to modify default behavior of ignoring all HDMI logical input
219     * device.
220     *
221     * @param deviceInfo {@link HdmiDeviceInfo} object just removed.
222     * @hide
223     */
224    @SystemApi
225    public String onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) {
226        return null;
227    }
228
229    private boolean isPassthroughInput(String inputId) {
230        if (mTvInputManager == null) {
231            mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
232        }
233        TvInputInfo info = mTvInputManager.getTvInputInfo(inputId);
234        if (info != null && info.isPassthroughInput()) {
235            return true;
236        }
237        return false;
238    }
239
240    /**
241     * Base class for derived classes to implement to provide a TV input session.
242     */
243    public abstract static class Session implements KeyEvent.Callback {
244        private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
245        private final WindowManager mWindowManager;
246        final Handler mHandler;
247        private WindowManager.LayoutParams mWindowParams;
248        private Surface mSurface;
249        private View mOverlayView;
250        private boolean mOverlayViewEnabled;
251        private IBinder mWindowToken;
252        private Rect mOverlayFrame;
253        private ITvInputSessionCallback mSessionCallback;
254
255        /**
256         * Creates a new Session.
257         *
258         * @param context The context of the application
259         */
260        public Session(Context context) {
261            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
262            mHandler = new Handler(context.getMainLooper());
263        }
264
265        /**
266         * Enables or disables the overlay view. By default, the overlay view is disabled. Must be
267         * called explicitly after the session is created to enable the overlay view.
268         *
269         * @param enable {@code true} if you want to enable the overlay view. {@code false}
270         *            otherwise.
271         */
272        public void setOverlayViewEnabled(final boolean enable) {
273            mHandler.post(new Runnable() {
274                @Override
275                public void run() {
276                    if (enable == mOverlayViewEnabled) {
277                        return;
278                    }
279                    mOverlayViewEnabled = enable;
280                    if (enable) {
281                        if (mWindowToken != null) {
282                            createOverlayView(mWindowToken, mOverlayFrame);
283                        }
284                    } else {
285                        removeOverlayView(false);
286                    }
287                }
288            });
289        }
290
291        /**
292         * Dispatches an event to the application using this session.
293         *
294         * @param eventType The type of the event.
295         * @param eventArgs Optional arguments of the event.
296         * @hide
297         */
298        public void notifySessionEvent(final String eventType, final Bundle eventArgs) {
299            if (eventType == null) {
300                throw new IllegalArgumentException("eventType should not be null.");
301            }
302            runOnMainThread(new Runnable() {
303                @Override
304                public void run() {
305                    try {
306                        if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")");
307                        mSessionCallback.onSessionEvent(eventType, eventArgs);
308                    } catch (RemoteException e) {
309                        Log.w(TAG, "error in sending event (event=" + eventType + ")");
310                    }
311                }
312            });
313        }
314
315        /**
316         * Notifies the channel of the session is retuned by TV input.
317         *
318         * @param channelUri The URI of a channel.
319         */
320        public void notifyChannelRetuned(final Uri channelUri) {
321            runOnMainThread(new Runnable() {
322                @Override
323                public void run() {
324                    try {
325                        if (DEBUG) Log.d(TAG, "notifyChannelRetuned");
326                        mSessionCallback.onChannelRetuned(channelUri);
327                    } catch (RemoteException e) {
328                        Log.w(TAG, "error in notifyChannelRetuned");
329                    }
330                }
331            });
332        }
333
334        /**
335         * Sends the list of all audio/video/subtitle tracks. The is used by the framework to
336         * maintain the track information for a given session, which in turn is used by
337         * {@link TvView#getTracks} for the application to retrieve metadata for a given track type.
338         * The TV input service must call this method as soon as the track information becomes
339         * available or is updated. Note that in a case where a part of the information for a
340         * certain track is updated, it is not necessary to create a new {@link TvTrackInfo} object
341         * with a different track ID.
342         *
343         * @param tracks A list which includes track information.
344         * @throws IllegalArgumentException if {@code tracks} contains redundant tracks.
345         */
346        public void notifyTracksChanged(final List<TvTrackInfo> tracks) {
347            Set<String> trackIdSet = new HashSet<String>();
348            for (TvTrackInfo track : tracks) {
349                String trackId = track.getId();
350                if (trackIdSet.contains(trackId)) {
351                    throw new IllegalArgumentException("redundant track ID: " + trackId);
352                }
353                trackIdSet.add(trackId);
354            }
355            trackIdSet.clear();
356
357            // TODO: Validate the track list.
358            runOnMainThread(new Runnable() {
359                @Override
360                public void run() {
361                    try {
362                        if (DEBUG) Log.d(TAG, "notifyTracksChanged");
363                        mSessionCallback.onTracksChanged(tracks);
364                    } catch (RemoteException e) {
365                        Log.w(TAG, "error in notifyTracksChanged");
366                    }
367                }
368            });
369        }
370
371        /**
372         * Sends the type and ID of a selected track. This is used to inform the application that a
373         * specific track is selected. The TV input service must call this method as soon as a track
374         * is selected either by default or in response to a call to {@link #onSelectTrack}. The
375         * selected track ID for a given type is maintained in the framework until the next call to
376         * this method even after the entire track list is updated (but is reset when the session is
377         * tuned to a new channel), so care must be taken not to result in an obsolete track ID.
378         *
379         * @param type The type of the selected track. The type can be
380         *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
381         *            {@link TvTrackInfo#TYPE_SUBTITLE}.
382         * @param trackId The ID of the selected track.
383         * @see #onSelectTrack
384         */
385        public void notifyTrackSelected(final int type, final String trackId) {
386            runOnMainThread(new Runnable() {
387                @Override
388                public void run() {
389                    try {
390                        if (DEBUG) Log.d(TAG, "notifyTrackSelected");
391                        mSessionCallback.onTrackSelected(type, trackId);
392                    } catch (RemoteException e) {
393                        Log.w(TAG, "error in notifyTrackSelected");
394                    }
395                }
396            });
397        }
398
399        /**
400         * Informs the application that the video is now available for watching. This is primarily
401         * used to signal the application to unblock the screen. The TV input service must call this
402         * method as soon as the content rendered onto its surface gets ready for viewing.
403         *
404         * @see #notifyVideoUnavailable
405         */
406        public void notifyVideoAvailable() {
407            runOnMainThread(new Runnable() {
408                @Override
409                public void run() {
410                    try {
411                        if (DEBUG) Log.d(TAG, "notifyVideoAvailable");
412                        mSessionCallback.onVideoAvailable();
413                    } catch (RemoteException e) {
414                        Log.w(TAG, "error in notifyVideoAvailable");
415                    }
416                }
417            });
418        }
419
420        /**
421         * Informs the application that the video became unavailable for some reason. This is
422         * primarily used to signal the application to block the screen not to show any intermittent
423         * video artifacts.
424         *
425         * @param reason The reason why the video became unavailable:
426         *            <ul>
427         *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
428         *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
429         *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
430         *            <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
431         *            </ul>
432         * @see #notifyVideoAvailable
433         */
434        public void notifyVideoUnavailable(final int reason) {
435            if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START
436                    || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) {
437                throw new IllegalArgumentException("Unknown reason: " + reason);
438            }
439            runOnMainThread(new Runnable() {
440                @Override
441                public void run() {
442                    try {
443                        if (DEBUG) Log.d(TAG, "notifyVideoUnavailable");
444                        mSessionCallback.onVideoUnavailable(reason);
445                    } catch (RemoteException e) {
446                        Log.w(TAG, "error in notifyVideoUnavailable");
447                    }
448                }
449            });
450        }
451
452        /**
453         * Informs the application that the user is allowed to watch the current program content.
454         * <p>
455         * Each TV input service is required to query the system whether the user is allowed to
456         * watch the current program before showing it to the user if the parental controls is
457         * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
458         * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
459         * service should block the content or not is determined by invoking
460         * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
461         * with the content rating for the current program. Then the {@link TvInputManager} makes a
462         * judgment based on the user blocked ratings stored in the secure settings and returns the
463         * result. If the rating in question turns out to be allowed by the user, the TV input
464         * service must call this method to notify the application that is permitted to show the
465         * content.
466         * </p><p>
467         * Each TV input service also needs to continuously listen to any changes made to the
468         * parental controls settings by registering a broadcast receiver to receive
469         * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
470         * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
471         * reevaluate the current program with the new parental controls settings.
472         * </p>
473         *
474         * @see #notifyContentBlocked
475         * @see TvInputManager
476         */
477        public void notifyContentAllowed() {
478            runOnMainThread(new Runnable() {
479                @Override
480                public void run() {
481                    try {
482                        if (DEBUG) Log.d(TAG, "notifyContentAllowed");
483                        mSessionCallback.onContentAllowed();
484                    } catch (RemoteException e) {
485                        Log.w(TAG, "error in notifyContentAllowed");
486                    }
487                }
488            });
489        }
490
491        /**
492         * Informs the application that the current program content is blocked by parent controls.
493         * <p>
494         * Each TV input service is required to query the system whether the user is allowed to
495         * watch the current program before showing it to the user if the parental controls is
496         * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled
497         * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input
498         * service should block the content or not is determined by invoking
499         * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)}
500         * with the content rating for the current program. Then the {@link TvInputManager} makes a
501         * judgment based on the user blocked ratings stored in the secure settings and returns the
502         * result. If the rating in question turns out to be blocked, the TV input service must
503         * immediately block the content and call this method with the content rating of the current
504         * program to prompt the PIN verification screen.
505         * </p><p>
506         * Each TV input service also needs to continuously listen to any changes made to the
507         * parental controls settings by registering a broadcast receiver to receive
508         * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and
509         * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately
510         * reevaluate the current program with the new parental controls settings.
511         * </p>
512         *
513         * @param rating The content rating for the current TV program.
514         * @see #notifyContentAllowed
515         * @see TvInputManager
516         */
517        public void notifyContentBlocked(final TvContentRating rating) {
518            runOnMainThread(new Runnable() {
519                @Override
520                public void run() {
521                    try {
522                        if (DEBUG) Log.d(TAG, "notifyContentBlocked");
523                        mSessionCallback.onContentBlocked(rating.flattenToString());
524                    } catch (RemoteException e) {
525                        Log.w(TAG, "error in notifyContentBlocked");
526                    }
527                }
528            });
529        }
530
531        /**
532         * Assigns a position of the {@link Surface} passed by {@link #onSetSurface}. The position
533         * is relative to an overlay view.
534         *
535         * @param left Left position in pixels, relative to the overlay view.
536         * @param top Top position in pixels, relative to the overlay view.
537         * @param right Right position in pixels, relative to the overlay view.
538         * @param bottm Bottom position in pixels, relative to the overlay view.
539         * @see #onOverlayViewSizeChanged
540         * @hide
541         */
542        @SystemApi
543        public void layoutSurface(final int left, final int top, final int right, final int bottm) {
544            if (left > right || top > bottm) {
545                throw new IllegalArgumentException("Invalid parameter");
546            }
547            runOnMainThread(new Runnable() {
548                @Override
549                public void run() {
550                    try {
551                        if (DEBUG) Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top + ", r="
552                                + right + ", b=" + bottm + ",)");
553                        mSessionCallback.onLayoutSurface(left, top, right, bottm);
554                    } catch (RemoteException e) {
555                        Log.w(TAG, "error in layoutSurface");
556                    }
557                }
558            });
559        }
560
561        /**
562         * Called when the session is released.
563         */
564        public abstract void onRelease();
565
566        /**
567         * Sets the current session as the main session. The main session is a session whose
568         * corresponding TV input determines the HDMI-CEC active source device.
569         * <p>
570         * TV input service that manages HDMI-CEC logical device should implement {@link
571         * #onSetMain} to (1) select the corresponding HDMI logical device as the source device
572         * when {@code isMain} is {@code true}, and to (2) select the internal device (= TV itself)
573         * as the source device when {@code isMain} is {@code false} and the session is still main.
574         * Also, if a surface is passed to a non-main session and active source is changed to
575         * initiate the surface, the active source should be returned to the main session.
576         * </p><p>
577         * {@link TvView} guarantees that, when tuning involves a session transition, {@code
578         * onSetMain(true)} for new session is called first, {@code onSetMain(false)} for old
579         * session is called afterwards. This allows {@code onSetMain(false)} to be no-op when TV
580         * input service knows that the next main session corresponds to another HDMI logical
581         * device. Practically, this implies that one TV input service should handle all HDMI port
582         * and HDMI-CEC logical devices for smooth active source transition.
583         * </p>
584         *
585         * @param isMain If true, session should become main.
586         * @see TvView#setMain
587         * @hide
588         */
589        @SystemApi
590        public void onSetMain(boolean isMain) {
591        }
592
593        /**
594         * Sets the {@link Surface} for the current input session on which the TV input renders
595         * video.
596         *
597         * @param surface {@link Surface} an application passes to this TV input session.
598         * @return {@code true} if the surface was set, {@code false} otherwise.
599         */
600        public abstract boolean onSetSurface(Surface surface);
601
602        /**
603         * Called after any structural changes (format or size) have been made to the
604         * {@link Surface} passed by {@link #onSetSurface}. This method is always called
605         * at least once, after {@link #onSetSurface} with non-null {@link Surface} is called.
606         *
607         * @param format The new PixelFormat of the {@link Surface}.
608         * @param width The new width of the {@link Surface}.
609         * @param height The new height of the {@link Surface}.
610         */
611        public void onSurfaceChanged(int format, int width, int height) {
612        }
613
614        /**
615         * Called when a size of an overlay view is changed by an application. Even when the overlay
616         * view is disabled by {@link #setOverlayViewEnabled}, this is called. The size is same as
617         * the size of {@link Surface} in general. Once {@link #layoutSurface} is called, the sizes
618         * of {@link Surface} and the overlay view can be different.
619         *
620         * @param width The width of the overlay view.
621         * @param height The height of the overlay view.
622         * @hide
623         */
624        @SystemApi
625        public void onOverlayViewSizeChanged(int width, int height) {
626        }
627
628        /**
629         * Sets the relative stream volume of the current TV input session to handle the change of
630         * audio focus by setting.
631         *
632         * @param volume Volume scale from 0.0 to 1.0.
633         */
634        public abstract void onSetStreamVolume(float volume);
635
636        /**
637         * Tunes to a given channel. When the video is available, {@link #notifyVideoAvailable()}
638         * should be called. Also, {@link #notifyVideoUnavailable(int)} should be called when the
639         * TV input cannot continue playing the given channel.
640         *
641         * @param channelUri The URI of the channel.
642         * @return {@code true} the tuning was successful, {@code false} otherwise.
643         */
644        public abstract boolean onTune(Uri channelUri);
645
646        /**
647         * Calls {@link #onTune(Uri)}. Override this method in order to handle {@code params}.
648         *
649         * @param channelUri The URI of the channel.
650         * @param params The extra parameters from other applications.
651         * @return {@code true} the tuning was successful, {@code false} otherwise.
652         * @hide
653         */
654        @SystemApi
655        public boolean onTune(Uri channelUri, Bundle params) {
656            return onTune(channelUri);
657        }
658
659        /**
660         * Enables or disables the caption.
661         * <p>
662         * The locale for the user's preferred captioning language can be obtained by calling
663         * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}.
664         *
665         * @param enabled {@code true} to enable, {@code false} to disable.
666         * @see CaptioningManager
667         */
668        public abstract void onSetCaptionEnabled(boolean enabled);
669
670        /**
671         * Requests to unblock the content according to the given rating.
672         * <p>
673         * The implementation should unblock the content.
674         * TV input service has responsibility to decide when/how the unblock expires
675         * while it can keep previously unblocked ratings in order not to ask a user
676         * to unblock whenever a content rating is changed.
677         * Therefore an unblocked rating can be valid for a channel, a program,
678         * or certain amount of time depending on the implementation.
679         * </p>
680         *
681         * @param unblockedRating An unblocked content rating
682         */
683        public void onUnblockContent(TvContentRating unblockedRating) {
684        }
685
686        /**
687         * Select a given track.
688         * <p>
689         * If this is done successfully, the implementation should call {@link #notifyTrackSelected}
690         * to help applications maintain the selcted track lists.
691         * </p>
692         *
693         * @param trackId The ID of the track to select. {@code null} means to unselect the current
694         *            track for a given type.
695         * @param type The type of the track to select. The type can be
696         *            {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or
697         *            {@link TvTrackInfo#TYPE_SUBTITLE}.
698         * @see #notifyTrackSelected
699         */
700        public boolean onSelectTrack(int type, String trackId) {
701            return false;
702        }
703
704        /**
705         * Processes a private command sent from the application to the TV input. This can be used
706         * to provide domain-specific features that are only known between certain TV inputs and
707         * their clients.
708         *
709         * @param action Name of the command to be performed. This <em>must</em> be a scoped name,
710         *            i.e. prefixed with a package name you own, so that different developers will
711         *            not create conflicting commands.
712         * @param data Any data to include with the command.
713         * @hide
714         */
715        @SystemApi
716        public void onAppPrivateCommand(String action, Bundle data) {
717        }
718
719        /**
720         * Called when an application requests to create an overlay view. Each session
721         * implementation can override this method and return its own view.
722         *
723         * @return a view attached to the overlay window
724         */
725        public View onCreateOverlayView() {
726            return null;
727        }
728
729        /**
730         * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent)
731         * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event).
732         * <p>
733         * Override this to intercept key down events before they are processed by the application.
734         * If you return true, the application will not process the event itself. If you return
735         * false, the normal application processing will occur as if the TV input had not seen the
736         * event at all.
737         *
738         * @param keyCode The value in event.getKeyCode().
739         * @param event Description of the key event.
740         * @return If you handled the event, return {@code true}. If you want to allow the event to
741         *         be handled by the next receiver, return {@code false}.
742         */
743        @Override
744        public boolean onKeyDown(int keyCode, KeyEvent event) {
745            return false;
746        }
747
748        /**
749         * Default implementation of
750         * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
751         * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event).
752         * <p>
753         * Override this to intercept key long press events before they are processed by the
754         * application. If you return true, the application will not process the event itself. If
755         * you return false, the normal application processing will occur as if the TV input had not
756         * seen the event at all.
757         *
758         * @param keyCode The value in event.getKeyCode().
759         * @param event Description of the key event.
760         * @return If you handled the event, return {@code true}. If you want to allow the event to
761         *         be handled by the next receiver, return {@code false}.
762         */
763        @Override
764        public boolean onKeyLongPress(int keyCode, KeyEvent event) {
765            return false;
766        }
767
768        /**
769         * Default implementation of
770         * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
771         * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event).
772         * <p>
773         * Override this to intercept special key multiple events before they are processed by the
774         * application. If you return true, the application will not itself process the event. If
775         * you return false, the normal application processing will occur as if the TV input had not
776         * seen the event at all.
777         *
778         * @param keyCode The value in event.getKeyCode().
779         * @param count The number of times the action was made.
780         * @param event Description of the key event.
781         * @return If you handled the event, return {@code true}. If you want to allow the event to
782         *         be handled by the next receiver, return {@code false}.
783         */
784        @Override
785        public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
786            return false;
787        }
788
789        /**
790         * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent)
791         * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event).
792         * <p>
793         * Override this to intercept key up events before they are processed by the application. If
794         * you return true, the application will not itself process the event. If you return false,
795         * the normal application processing will occur as if the TV input had not seen the event at
796         * all.
797         *
798         * @param keyCode The value in event.getKeyCode().
799         * @param event Description of the key event.
800         * @return If you handled the event, return {@code true}. If you want to allow the event to
801         *         be handled by the next receiver, return {@code false}.
802         */
803        @Override
804        public boolean onKeyUp(int keyCode, KeyEvent event) {
805            return false;
806        }
807
808        /**
809         * Implement this method to handle touch screen motion events on the current input session.
810         *
811         * @param event The motion event being received.
812         * @return If you handled the event, return {@code true}. If you want to allow the event to
813         *         be handled by the next receiver, return {@code false}.
814         * @see View#onTouchEvent
815         */
816        public boolean onTouchEvent(MotionEvent event) {
817            return false;
818        }
819
820        /**
821         * Implement this method to handle trackball events on the current input session.
822         *
823         * @param event The motion event being received.
824         * @return If you handled the event, return {@code true}. If you want to allow the event to
825         *         be handled by the next receiver, return {@code false}.
826         * @see View#onTrackballEvent
827         */
828        public boolean onTrackballEvent(MotionEvent event) {
829            return false;
830        }
831
832        /**
833         * Implement this method to handle generic motion events on the current input session.
834         *
835         * @param event The motion event being received.
836         * @return If you handled the event, return {@code true}. If you want to allow the event to
837         *         be handled by the next receiver, return {@code false}.
838         * @see View#onGenericMotionEvent
839         */
840        public boolean onGenericMotionEvent(MotionEvent event) {
841            return false;
842        }
843
844        /**
845         * This method is called when the application would like to stop using the current input
846         * session.
847         */
848        void release() {
849            removeOverlayView(true);
850            onRelease();
851            if (mSurface != null) {
852                mSurface.release();
853                mSurface = null;
854            }
855        }
856
857        /**
858         * Calls {@link #onSetMain}.
859         */
860        void setMain(boolean isMain) {
861            onSetMain(isMain);
862        }
863
864        /**
865         * Calls {@link #onSetSurface}.
866         */
867        void setSurface(Surface surface) {
868            onSetSurface(surface);
869            if (mSurface != null) {
870                mSurface.release();
871            }
872            mSurface = surface;
873            // TODO: Handle failure.
874        }
875
876        /**
877         * Calls {@link #onSurfaceChanged}.
878         */
879        void dispatchSurfaceChanged(int format, int width, int height) {
880            if (DEBUG) {
881                Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
882                        + ", height=" + height + ")");
883            }
884            onSurfaceChanged(format, width, height);
885        }
886
887        /**
888         * Calls {@link #onSetStreamVolume}.
889         */
890        void setStreamVolume(float volume) {
891            onSetStreamVolume(volume);
892        }
893
894        /**
895         * Calls {@link #onTune}.
896         */
897        void tune(Uri channelUri, Bundle params) {
898            onTune(channelUri, params);
899            // TODO: Handle failure.
900        }
901
902        /**
903         * Calls {@link #onSetCaptionEnabled}.
904         */
905        void setCaptionEnabled(boolean enabled) {
906            onSetCaptionEnabled(enabled);
907        }
908
909        /**
910         * Calls {@link #onSelectTrack}.
911         */
912        void selectTrack(int type, String trackId) {
913            onSelectTrack(type, trackId);
914        }
915
916        /**
917         * Calls {@link #onUnblockContent}.
918         */
919        void unblockContent(String unblockedRating) {
920            onUnblockContent(TvContentRating.unflattenFromString(unblockedRating));
921            // TODO: Handle failure.
922        }
923
924        /**
925         * Calls {@link #onAppPrivateCommand}.
926         */
927        void appPrivateCommand(String action, Bundle data) {
928            onAppPrivateCommand(action, data);
929        }
930
931        /**
932         * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach
933         * to the overlay window.
934         *
935         * @param windowToken A window token of an application.
936         * @param frame A position of the overlay view.
937         */
938        void createOverlayView(IBinder windowToken, Rect frame) {
939            if (mOverlayView != null) {
940                mWindowManager.removeView(mOverlayView);
941                mOverlayView = null;
942            }
943            if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")");
944            mWindowToken = windowToken;
945            mOverlayFrame = frame;
946            onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
947            if (!mOverlayViewEnabled) {
948                return;
949            }
950            mOverlayView = onCreateOverlayView();
951            if (mOverlayView == null) {
952                return;
953            }
954            // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create
955            // an overlay window above the media window but below the application window.
956            int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
957            // We make the overlay view non-focusable and non-touchable so that
958            // the application that owns the window token can decide whether to consume or
959            // dispatch the input events.
960            int flag = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
961                    | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
962                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
963            mWindowParams = new WindowManager.LayoutParams(
964                    frame.right - frame.left, frame.bottom - frame.top,
965                    frame.left, frame.top, type, flag, PixelFormat.TRANSPARENT);
966            mWindowParams.privateFlags |=
967                    WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
968            mWindowParams.gravity = Gravity.START | Gravity.TOP;
969            mWindowParams.token = windowToken;
970            mWindowManager.addView(mOverlayView, mWindowParams);
971        }
972
973        /**
974         * Relayouts the current overlay view.
975         *
976         * @param frame A new position of the overlay view.
977         */
978        void relayoutOverlayView(Rect frame) {
979            if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")");
980            if (mOverlayFrame == null || mOverlayFrame.width() != frame.width()
981                    || mOverlayFrame.height() != frame.height()) {
982                // Note: relayoutOverlayView is called whenever TvView's layout is changed
983                // regardless of setOverlayViewEnabled.
984                onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top);
985            }
986            mOverlayFrame = frame;
987            if (!mOverlayViewEnabled || mOverlayView == null) {
988                return;
989            }
990            mWindowParams.x = frame.left;
991            mWindowParams.y = frame.top;
992            mWindowParams.width = frame.right - frame.left;
993            mWindowParams.height = frame.bottom - frame.top;
994            mWindowManager.updateViewLayout(mOverlayView, mWindowParams);
995        }
996
997        /**
998         * Removes the current overlay view.
999         */
1000        void removeOverlayView(boolean clearWindowToken) {
1001            if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayView + ")");
1002            if (clearWindowToken) {
1003                mWindowToken = null;
1004                mOverlayFrame = null;
1005            }
1006            if (mOverlayView != null) {
1007                mWindowManager.removeView(mOverlayView);
1008                mOverlayView = null;
1009                mWindowParams = null;
1010            }
1011        }
1012
1013        /**
1014         * Takes care of dispatching incoming input events and tells whether the event was handled.
1015         */
1016        int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
1017            if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
1018            boolean isNavigationKey = false;
1019            if (event instanceof KeyEvent) {
1020                KeyEvent keyEvent = (KeyEvent) event;
1021                isNavigationKey = isNavigationKey(keyEvent.getKeyCode());
1022                if (keyEvent.dispatch(this, mDispatcherState, this)) {
1023                    return TvInputManager.Session.DISPATCH_HANDLED;
1024                }
1025            } else if (event instanceof MotionEvent) {
1026                MotionEvent motionEvent = (MotionEvent) event;
1027                final int source = motionEvent.getSource();
1028                if (motionEvent.isTouchEvent()) {
1029                    if (onTouchEvent(motionEvent)) {
1030                        return TvInputManager.Session.DISPATCH_HANDLED;
1031                    }
1032                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
1033                    if (onTrackballEvent(motionEvent)) {
1034                        return TvInputManager.Session.DISPATCH_HANDLED;
1035                    }
1036                } else {
1037                    if (onGenericMotionEvent(motionEvent)) {
1038                        return TvInputManager.Session.DISPATCH_HANDLED;
1039                    }
1040                }
1041            }
1042            if (mOverlayView == null || !mOverlayView.isAttachedToWindow()) {
1043                return TvInputManager.Session.DISPATCH_NOT_HANDLED;
1044            }
1045            if (!mOverlayView.hasWindowFocus()) {
1046                mOverlayView.getViewRootImpl().windowFocusChanged(true, true);
1047            }
1048            if (isNavigationKey && mOverlayView.hasFocusable()) {
1049                // If mOverlayView has focusable views, navigation key events should be always
1050                // handled. If not, it can make the application UI navigation messed up.
1051                // For example, in the case that the left-most view is focused, a left key event
1052                // will not be handled in ViewRootImpl. Then, the left key event will be handled in
1053                // the application during the UI navigation of the TV input.
1054                mOverlayView.getViewRootImpl().dispatchInputEvent(event);
1055                return TvInputManager.Session.DISPATCH_HANDLED;
1056            } else {
1057                mOverlayView.getViewRootImpl().dispatchInputEvent(event, receiver);
1058                return TvInputManager.Session.DISPATCH_IN_PROGRESS;
1059            }
1060        }
1061
1062        private void setSessionCallback(ITvInputSessionCallback callback) {
1063            mSessionCallback = callback;
1064        }
1065
1066        private final void runOnMainThread(Runnable action) {
1067            if (mHandler.getLooper().isCurrentThread() && mSessionCallback != null) {
1068                action.run();
1069            } else {
1070                // Posts the runnable if this is not called from the main thread or the session
1071                // is not initialized yet.
1072                mHandler.post(action);
1073            }
1074        }
1075    }
1076
1077    /**
1078     * Base class for a TV input session which represents an external device connected to a
1079     * hardware TV input.
1080     * <p>
1081     * This class is for an input which provides channels for the external set-top box to the
1082     * application. Once a TV input returns an implementation of this class on
1083     * {@link #onCreateSession(String)}, the framework will create a separate session for
1084     * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so
1085     * that the user can see the screen of the hardware TV Input when she tunes to a channel from
1086     * this TV input. The implementation of this class is expected to change the channel of the
1087     * external set-top box via a proprietary protocol when {@link HardwareSession#onTune(Uri)} is
1088     * requested by the application.
1089     * </p><p>
1090     * Note that this class is not for inputs for internal hardware like built-in tuner and HDMI 1.
1091     * </p>
1092     * @see #onCreateSession(String)
1093     */
1094    public abstract static class HardwareSession extends Session {
1095
1096        /**
1097         * Creates a new HardwareSession.
1098         *
1099         * @param context The context of the application
1100         */
1101        public HardwareSession(Context context) {
1102            super(context);
1103        }
1104
1105        private TvInputManager.Session mHardwareSession;
1106        private ITvInputSession mProxySession;
1107        private ITvInputSessionCallback mProxySessionCallback;
1108        private Handler mServiceHandler;
1109
1110        /**
1111         * Returns the hardware TV input ID the external device is connected to.
1112         * <p>
1113         * TV input is expected to provide {@link android.R.attr#setupActivity} so that
1114         * the application can launch it before using this TV input. The setup activity may let
1115         * the user select the hardware TV input to which the external device is connected. The ID
1116         * of the selected one should be stored in the TV input so that it can be returned here.
1117         * </p>
1118         */
1119        public abstract String getHardwareInputId();
1120
1121        private final TvInputManager.SessionCallback mHardwareSessionCallback =
1122                new TvInputManager.SessionCallback() {
1123            @Override
1124            public void onSessionCreated(TvInputManager.Session session) {
1125                mHardwareSession = session;
1126                SomeArgs args = SomeArgs.obtain();
1127                if (session != null) {
1128                    args.arg1 = mProxySession;
1129                    args.arg2 = mProxySessionCallback;
1130                    args.arg3 = session.getToken();
1131                } else {
1132                    args.arg1 = null;
1133                    args.arg2 = mProxySessionCallback;
1134                    args.arg3 = null;
1135                    onRelease();
1136                }
1137                mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, args)
1138                        .sendToTarget();
1139            }
1140
1141            @Override
1142            public void onVideoAvailable(final TvInputManager.Session session) {
1143                if (mHardwareSession == session) {
1144                    onHardwareVideoAvailable();
1145                }
1146            }
1147
1148            @Override
1149            public void onVideoUnavailable(final TvInputManager.Session session,
1150                    final int reason) {
1151                if (mHardwareSession == session) {
1152                    onHardwareVideoUnavailable(reason);
1153                }
1154            }
1155        };
1156
1157        /**
1158         * This method will not be called in {@link HardwareSession}. Framework will
1159         * forward the application's surface to the hardware TV input.
1160         */
1161        @Override
1162        public final boolean onSetSurface(Surface surface) {
1163            Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession.");
1164            return false;
1165        }
1166
1167        /**
1168         * Called when the underlying hardware TV input session calls
1169         * {@link TvInputService.Session#notifyVideoAvailable()}.
1170         */
1171        public void onHardwareVideoAvailable() { }
1172
1173        /**
1174         * Called when the underlying hardware TV input session calls
1175         * {@link TvInputService.Session#notifyVideoUnavailable(int)}.
1176         *
1177         * @param reason The reason that the hardware TV input stopped the playback:
1178         * <ul>
1179         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN}
1180         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING}
1181         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL}
1182         * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING}
1183         * </ul>
1184         */
1185        public void onHardwareVideoUnavailable(int reason) { }
1186    }
1187
1188    /** @hide */
1189    public static boolean isNavigationKey(int keyCode) {
1190        switch (keyCode) {
1191            case KeyEvent.KEYCODE_DPAD_LEFT:
1192            case KeyEvent.KEYCODE_DPAD_RIGHT:
1193            case KeyEvent.KEYCODE_DPAD_UP:
1194            case KeyEvent.KEYCODE_DPAD_DOWN:
1195            case KeyEvent.KEYCODE_DPAD_CENTER:
1196            case KeyEvent.KEYCODE_PAGE_UP:
1197            case KeyEvent.KEYCODE_PAGE_DOWN:
1198            case KeyEvent.KEYCODE_MOVE_HOME:
1199            case KeyEvent.KEYCODE_MOVE_END:
1200            case KeyEvent.KEYCODE_TAB:
1201            case KeyEvent.KEYCODE_SPACE:
1202            case KeyEvent.KEYCODE_ENTER:
1203                return true;
1204        }
1205        return false;
1206    }
1207
1208    @SuppressLint("HandlerLeak")
1209    private final class ServiceHandler extends Handler {
1210        private static final int DO_CREATE_SESSION = 1;
1211        private static final int DO_NOTIFY_SESSION_CREATED = 2;
1212        private static final int DO_ADD_HARDWARE_TV_INPUT = 3;
1213        private static final int DO_REMOVE_HARDWARE_TV_INPUT = 4;
1214        private static final int DO_ADD_HDMI_TV_INPUT = 5;
1215        private static final int DO_REMOVE_HDMI_TV_INPUT = 6;
1216
1217        private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) {
1218            int n = mCallbacks.beginBroadcast();
1219            for (int i = 0; i < n; ++i) {
1220                try {
1221                    mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo);
1222                } catch (RemoteException e) {
1223                    Log.e(TAG, "Error while broadcasting.", e);
1224                }
1225            }
1226            mCallbacks.finishBroadcast();
1227        }
1228
1229        private void broadcastAddHdmiTvInput(int id, TvInputInfo inputInfo) {
1230            int n = mCallbacks.beginBroadcast();
1231            for (int i = 0; i < n; ++i) {
1232                try {
1233                    mCallbacks.getBroadcastItem(i).addHdmiTvInput(id, inputInfo);
1234                } catch (RemoteException e) {
1235                    Log.e(TAG, "Error while broadcasting.", e);
1236                }
1237            }
1238            mCallbacks.finishBroadcast();
1239        }
1240
1241        private void broadcastRemoveTvInput(String inputId) {
1242            int n = mCallbacks.beginBroadcast();
1243            for (int i = 0; i < n; ++i) {
1244                try {
1245                    mCallbacks.getBroadcastItem(i).removeTvInput(inputId);
1246                } catch (RemoteException e) {
1247                    Log.e(TAG, "Error while broadcasting.", e);
1248                }
1249            }
1250            mCallbacks.finishBroadcast();
1251        }
1252
1253        @Override
1254        public final void handleMessage(Message msg) {
1255            switch (msg.what) {
1256                case DO_CREATE_SESSION: {
1257                    SomeArgs args = (SomeArgs) msg.obj;
1258                    InputChannel channel = (InputChannel) args.arg1;
1259                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
1260                    String inputId = (String) args.arg3;
1261                    args.recycle();
1262                    Session sessionImpl = onCreateSession(inputId);
1263                    if (sessionImpl == null) {
1264                        try {
1265                            // Failed to create a session.
1266                            cb.onSessionCreated(null, null);
1267                        } catch (RemoteException e) {
1268                            Log.e(TAG, "error in onSessionCreated");
1269                        }
1270                        return;
1271                    }
1272                    sessionImpl.setSessionCallback(cb);
1273                    ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this,
1274                            sessionImpl, channel);
1275                    if (sessionImpl instanceof HardwareSession) {
1276                        HardwareSession proxySession =
1277                                ((HardwareSession) sessionImpl);
1278                        String harewareInputId = proxySession.getHardwareInputId();
1279                        if (TextUtils.isEmpty(harewareInputId) ||
1280                                !isPassthroughInput(harewareInputId)) {
1281                            if (TextUtils.isEmpty(harewareInputId)) {
1282                                Log.w(TAG, "Hardware input id is not setup yet.");
1283                            } else {
1284                                Log.w(TAG, "Invalid hardware input id : " + harewareInputId);
1285                            }
1286                            sessionImpl.onRelease();
1287                            try {
1288                                cb.onSessionCreated(null, null);
1289                            } catch (RemoteException e) {
1290                                Log.e(TAG, "error in onSessionCreated");
1291                            }
1292                            return;
1293                        }
1294                        proxySession.mProxySession = stub;
1295                        proxySession.mProxySessionCallback = cb;
1296                        proxySession.mServiceHandler = mServiceHandler;
1297                        TvInputManager manager = (TvInputManager) getSystemService(
1298                                Context.TV_INPUT_SERVICE);
1299                        manager.createSession(harewareInputId,
1300                                proxySession.mHardwareSessionCallback, mServiceHandler);
1301                    } else {
1302                        SomeArgs someArgs = SomeArgs.obtain();
1303                        someArgs.arg1 = stub;
1304                        someArgs.arg2 = cb;
1305                        someArgs.arg3 = null;
1306                        mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED,
1307                                someArgs).sendToTarget();
1308                    }
1309                    return;
1310                }
1311                case DO_NOTIFY_SESSION_CREATED: {
1312                    SomeArgs args = (SomeArgs) msg.obj;
1313                    ITvInputSession stub = (ITvInputSession) args.arg1;
1314                    ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
1315                    IBinder hardwareSessionToken = (IBinder) args.arg3;
1316                    try {
1317                        cb.onSessionCreated(stub, hardwareSessionToken);
1318                    } catch (RemoteException e) {
1319                        Log.e(TAG, "error in onSessionCreated");
1320                    }
1321                    args.recycle();
1322                    return;
1323                }
1324                case DO_ADD_HARDWARE_TV_INPUT: {
1325                    TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1326                    TvInputInfo inputInfo = onHardwareAdded(hardwareInfo);
1327                    if (inputInfo != null) {
1328                        broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo);
1329                    }
1330                    return;
1331                }
1332                case DO_REMOVE_HARDWARE_TV_INPUT: {
1333                    TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj;
1334                    String inputId = onHardwareRemoved(hardwareInfo);
1335                    if (inputId != null) {
1336                        broadcastRemoveTvInput(inputId);
1337                    }
1338                    return;
1339                }
1340                case DO_ADD_HDMI_TV_INPUT: {
1341                    HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
1342                    TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo);
1343                    if (inputInfo != null) {
1344                        broadcastAddHdmiTvInput(deviceInfo.getId(), inputInfo);
1345                    }
1346                    return;
1347                }
1348                case DO_REMOVE_HDMI_TV_INPUT: {
1349                    HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
1350                    String inputId = onHdmiDeviceRemoved(deviceInfo);
1351                    if (inputId != null) {
1352                        broadcastRemoveTvInput(inputId);
1353                    }
1354                    return;
1355                }
1356                default: {
1357                    Log.w(TAG, "Unhandled message code: " + msg.what);
1358                    return;
1359                }
1360            }
1361        }
1362    }
1363}
1364