OSDN Git Service

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