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