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