TvInputService.java revision ff04ae757a5542d2d5633e75b7adacc4fce1ce7e
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.HdmiCecDeviceInfo; 27import android.net.Uri; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.IBinder; 31import android.os.Looper; 32import android.os.Message; 33import android.os.RemoteCallbackList; 34import android.os.RemoteException; 35import android.text.TextUtils; 36import android.util.Log; 37import android.view.Gravity; 38import android.view.InputChannel; 39import android.view.InputDevice; 40import android.view.InputEvent; 41import android.view.InputEventReceiver; 42import android.view.KeyEvent; 43import android.view.MotionEvent; 44import android.view.Surface; 45import android.view.SurfaceView; 46import android.view.View; 47import android.view.WindowManager; 48import android.view.accessibility.CaptioningManager; 49 50import com.android.internal.annotations.VisibleForTesting; 51import com.android.internal.os.SomeArgs; 52 53import java.util.HashSet; 54import java.util.List; 55import 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 */ 70public 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><{@link android.R.styleable#TvInputService tv-input}></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 final Handler mServiceHandler = new ServiceHandler(getMainLooper()); 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 public ServiceHandler(Looper looper) { 1171 super(looper); 1172 } 1173 1174 private void broadcastAddHardwareTvInput(int deviceId, TvInputInfo inputInfo) { 1175 int n = mCallbacks.beginBroadcast(); 1176 for (int i = 0; i < n; ++i) { 1177 try { 1178 mCallbacks.getBroadcastItem(i).addHardwareTvInput(deviceId, inputInfo); 1179 } catch (RemoteException e) { 1180 Log.e(TAG, "Error while broadcasting.", e); 1181 } 1182 } 1183 mCallbacks.finishBroadcast(); 1184 } 1185 1186 private void broadcastAddHdmiCecTvInput( 1187 int logicalAddress, TvInputInfo inputInfo) { 1188 int n = mCallbacks.beginBroadcast(); 1189 for (int i = 0; i < n; ++i) { 1190 try { 1191 mCallbacks.getBroadcastItem(i).addHdmiCecTvInput(logicalAddress, inputInfo); 1192 } catch (RemoteException e) { 1193 Log.e(TAG, "Error while broadcasting.", e); 1194 } 1195 } 1196 mCallbacks.finishBroadcast(); 1197 } 1198 1199 private void broadcastRemoveTvInput(String inputId) { 1200 int n = mCallbacks.beginBroadcast(); 1201 for (int i = 0; i < n; ++i) { 1202 try { 1203 mCallbacks.getBroadcastItem(i).removeTvInput(inputId); 1204 } catch (RemoteException e) { 1205 Log.e(TAG, "Error while broadcasting.", e); 1206 } 1207 } 1208 mCallbacks.finishBroadcast(); 1209 } 1210 1211 @Override 1212 public final void handleMessage(Message msg) { 1213 switch (msg.what) { 1214 case DO_CREATE_SESSION: { 1215 SomeArgs args = (SomeArgs) msg.obj; 1216 InputChannel channel = (InputChannel) args.arg1; 1217 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; 1218 String inputId = (String) args.arg3; 1219 Session sessionImpl = onCreateSession(inputId); 1220 args.recycle(); 1221 if (sessionImpl == null) { 1222 try { 1223 // Failed to create a session. 1224 cb.onSessionCreated(null, null); 1225 } catch (RemoteException e) { 1226 Log.e(TAG, "error in onSessionCreated"); 1227 } 1228 } else { 1229 sessionImpl.setSessionCallback(cb); 1230 ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, 1231 sessionImpl, channel); 1232 if (sessionImpl instanceof HardwareSession) { 1233 HardwareSession proxySession = 1234 ((HardwareSession) sessionImpl); 1235 String harewareInputId = proxySession.getHardwareInputId(); 1236 if (!TextUtils.isEmpty(harewareInputId)) { 1237 // TODO: check if the given ID is really hardware TV input. 1238 proxySession.mProxySession = stub; 1239 proxySession.mProxySessionCallback = cb; 1240 TvInputManager manager = (TvInputManager) getSystemService( 1241 Context.TV_INPUT_SERVICE); 1242 manager.createSession(harewareInputId, 1243 proxySession.mHardwareSessionCallback, mServiceHandler); 1244 } else { 1245 sessionImpl.onRelease(); 1246 Log.w(TAG, "Hardware input id is not setup yet."); 1247 try { 1248 cb.onSessionCreated(null, null); 1249 } catch (RemoteException e) { 1250 Log.e(TAG, "error in onSessionCreated"); 1251 } 1252 } 1253 } else { 1254 SomeArgs someArgs = SomeArgs.obtain(); 1255 someArgs.arg1 = stub; 1256 someArgs.arg2 = cb; 1257 someArgs.arg3 = null; 1258 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, 1259 someArgs).sendToTarget(); 1260 } 1261 } 1262 return; 1263 } 1264 case DO_NOTIFY_SESSION_CREATED: { 1265 SomeArgs args = (SomeArgs) msg.obj; 1266 ITvInputSession stub = (ITvInputSession) args.arg1; 1267 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; 1268 IBinder hardwareSessionToken = (IBinder) args.arg3; 1269 try { 1270 cb.onSessionCreated(stub, hardwareSessionToken); 1271 } catch (RemoteException e) { 1272 Log.e(TAG, "error in onSessionCreated"); 1273 } 1274 args.recycle(); 1275 return; 1276 } 1277 case DO_ADD_HARDWARE_TV_INPUT: { 1278 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj; 1279 TvInputInfo inputInfo = onHardwareAdded(hardwareInfo); 1280 if (inputInfo != null) { 1281 broadcastAddHardwareTvInput(hardwareInfo.getDeviceId(), inputInfo); 1282 } 1283 return; 1284 } 1285 case DO_REMOVE_HARDWARE_TV_INPUT: { 1286 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj; 1287 String inputId = onHardwareRemoved(hardwareInfo); 1288 if (inputId != null) { 1289 broadcastRemoveTvInput(inputId); 1290 } 1291 return; 1292 } 1293 case DO_ADD_HDMI_CEC_TV_INPUT: { 1294 HdmiCecDeviceInfo cecDeviceInfo = (HdmiCecDeviceInfo) msg.obj; 1295 TvInputInfo inputInfo = onHdmiCecDeviceAdded(cecDeviceInfo); 1296 if (inputInfo != null) { 1297 broadcastAddHdmiCecTvInput(cecDeviceInfo.getLogicalAddress(), inputInfo); 1298 } 1299 return; 1300 } 1301 case DO_REMOVE_HDMI_CEC_TV_INPUT: { 1302 HdmiCecDeviceInfo cecDeviceInfo = (HdmiCecDeviceInfo) msg.obj; 1303 String inputId = onHdmiCecDeviceRemoved(cecDeviceInfo); 1304 if (inputId != null) { 1305 broadcastRemoveTvInput(inputId); 1306 } 1307 return; 1308 } 1309 default: { 1310 Log.w(TAG, "Unhandled message code: " + msg.what); 1311 return; 1312 } 1313 } 1314 } 1315 } 1316} 1317