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