1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.tv; 18 19 import android.annotation.FloatRange; 20 import android.annotation.IntDef; 21 import android.annotation.MainThread; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SuppressLint; 25 import android.annotation.SystemApi; 26 import android.app.ActivityManager; 27 import android.app.Service; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.graphics.PixelFormat; 32 import android.graphics.Rect; 33 import android.hardware.hdmi.HdmiDeviceInfo; 34 import android.media.PlaybackParams; 35 import android.net.Uri; 36 import android.os.AsyncTask; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.IBinder; 41 import android.os.Message; 42 import android.os.Process; 43 import android.os.RemoteCallbackList; 44 import android.os.RemoteException; 45 import android.text.TextUtils; 46 import android.util.Log; 47 import android.view.Gravity; 48 import android.view.InputChannel; 49 import android.view.InputDevice; 50 import android.view.InputEvent; 51 import android.view.InputEventReceiver; 52 import android.view.KeyEvent; 53 import android.view.MotionEvent; 54 import android.view.Surface; 55 import android.view.View; 56 import android.view.ViewRootImpl; 57 import android.view.WindowManager; 58 import android.view.accessibility.CaptioningManager; 59 import android.widget.FrameLayout; 60 61 import com.android.internal.os.SomeArgs; 62 import com.android.internal.util.Preconditions; 63 64 import java.lang.annotation.Retention; 65 import java.lang.annotation.RetentionPolicy; 66 import java.util.ArrayList; 67 import java.util.List; 68 69 /** 70 * The TvInputService class represents a TV input or source such as HDMI or built-in tuner which 71 * provides pass-through video or broadcast TV programs. 72 * 73 * <p>Applications will not normally use this service themselves, instead relying on the standard 74 * interaction provided by {@link TvView}. Those implementing TV input services should normally do 75 * so by deriving from this class and providing their own session implementation based on 76 * {@link TvInputService.Session}. All TV input services must require that clients hold the 77 * {@link android.Manifest.permission#BIND_TV_INPUT} in order to interact with the service; if this 78 * permission is not specified in the manifest, the system will refuse to bind to that TV input 79 * service. 80 */ 81 public abstract class TvInputService extends Service { 82 private static final boolean DEBUG = false; 83 private static final String TAG = "TvInputService"; 84 85 private static final int DETACH_OVERLAY_VIEW_TIMEOUT_MS = 5000; 86 87 /** 88 * This is the interface name that a service implementing a TV input should say that it support 89 * -- that is, this is the action it uses for its intent filter. To be supported, the service 90 * must also require the {@link android.Manifest.permission#BIND_TV_INPUT} permission so that 91 * other applications cannot abuse it. 92 */ 93 public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService"; 94 95 /** 96 * Name under which a TvInputService component publishes information about itself. 97 * This meta-data must reference an XML resource containing an 98 * <code><{@link android.R.styleable#TvInputService tv-input}></code> 99 * tag. 100 */ 101 public static final String SERVICE_META_DATA = "android.media.tv.input"; 102 103 /** 104 * Prioirity hint from use case types. 105 * 106 * @hide 107 */ 108 @IntDef(prefix = "PRIORITY_HINT_USE_CASE_TYPE_", 109 value = {PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND, PRIORITY_HINT_USE_CASE_TYPE_SCAN, 110 PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK, PRIORITY_HINT_USE_CASE_TYPE_LIVE, 111 PRIORITY_HINT_USE_CASE_TYPE_RECORD}) 112 @Retention(RetentionPolicy.SOURCE) 113 public @interface PriorityHintUseCaseType {} 114 115 /** 116 * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String, 117 * int)}: Background. TODO Link: Tuner#Tuner(Context, string, int). 118 */ 119 public static final int PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND = 100; 120 121 /** 122 * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String, 123 * int)}: Scan. TODO Link: Tuner#Tuner(Context, string, int). 124 */ 125 public static final int PRIORITY_HINT_USE_CASE_TYPE_SCAN = 200; 126 127 /** 128 * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String, 129 * int)}: Playback. TODO Link: Tuner#Tuner(Context, string, int). 130 */ 131 public static final int PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK = 300; 132 133 /** 134 * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String, 135 * int)}: Live. TODO Link: Tuner#Tuner(Context, string, int). 136 */ 137 public static final int PRIORITY_HINT_USE_CASE_TYPE_LIVE = 400; 138 139 /** 140 * Use case of priority hint for {@link android.media.MediaCas#MediaCas(Context, int, String, 141 * int)}: Record. TODO Link: Tuner#Tuner(Context, string, int). 142 */ 143 public static final int PRIORITY_HINT_USE_CASE_TYPE_RECORD = 500; 144 145 /** 146 * Handler instance to handle request from TV Input Manager Service. Should be run in the main 147 * looper to be synchronously run with {@code Session.mHandler}. 148 */ 149 private final Handler mServiceHandler = new ServiceHandler(); 150 private final RemoteCallbackList<ITvInputServiceCallback> mCallbacks = 151 new RemoteCallbackList<>(); 152 153 private TvInputManager mTvInputManager; 154 155 @Override onBind(Intent intent)156 public final IBinder onBind(Intent intent) { 157 ITvInputService.Stub tvInputServiceBinder = new ITvInputService.Stub() { 158 @Override 159 public void registerCallback(ITvInputServiceCallback cb) { 160 if (cb != null) { 161 mCallbacks.register(cb); 162 } 163 } 164 165 @Override 166 public void unregisterCallback(ITvInputServiceCallback cb) { 167 if (cb != null) { 168 mCallbacks.unregister(cb); 169 } 170 } 171 172 @Override 173 public void createSession(InputChannel channel, ITvInputSessionCallback cb, 174 String inputId, String sessionId) { 175 if (channel == null) { 176 Log.w(TAG, "Creating session without input channel"); 177 } 178 if (cb == null) { 179 return; 180 } 181 SomeArgs args = SomeArgs.obtain(); 182 args.arg1 = channel; 183 args.arg2 = cb; 184 args.arg3 = inputId; 185 args.arg4 = sessionId; 186 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, 187 args).sendToTarget(); 188 } 189 190 @Override 191 public void createRecordingSession(ITvInputSessionCallback cb, String inputId, 192 String sessionId) { 193 if (cb == null) { 194 return; 195 } 196 SomeArgs args = SomeArgs.obtain(); 197 args.arg1 = cb; 198 args.arg2 = inputId; 199 args.arg3 = sessionId; 200 mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args) 201 .sendToTarget(); 202 } 203 204 @Override 205 public List<String> getAvailableExtensionInterfaceNames() { 206 return TvInputService.this.getAvailableExtensionInterfaceNames(); 207 } 208 209 @Override 210 public IBinder getExtensionInterface(String name) { 211 return TvInputService.this.getExtensionInterface(name); 212 } 213 214 @Override 215 public String getExtensionInterfacePermission(String name) { 216 return TvInputService.this.getExtensionInterfacePermission(name); 217 } 218 219 @Override 220 public void notifyHardwareAdded(TvInputHardwareInfo hardwareInfo) { 221 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HARDWARE_INPUT, 222 hardwareInfo).sendToTarget(); 223 } 224 225 @Override 226 public void notifyHardwareRemoved(TvInputHardwareInfo hardwareInfo) { 227 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HARDWARE_INPUT, 228 hardwareInfo).sendToTarget(); 229 } 230 231 @Override 232 public void notifyHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) { 233 mServiceHandler.obtainMessage(ServiceHandler.DO_ADD_HDMI_INPUT, 234 deviceInfo).sendToTarget(); 235 } 236 237 @Override 238 public void notifyHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) { 239 mServiceHandler.obtainMessage(ServiceHandler.DO_REMOVE_HDMI_INPUT, 240 deviceInfo).sendToTarget(); 241 } 242 243 @Override 244 public void notifyHdmiDeviceUpdated(HdmiDeviceInfo deviceInfo) { 245 mServiceHandler.obtainMessage(ServiceHandler.DO_UPDATE_HDMI_INPUT, 246 deviceInfo).sendToTarget(); 247 } 248 }; 249 IBinder ext = createExtension(); 250 if (ext != null) { 251 tvInputServiceBinder.setExtension(ext); 252 } 253 return tvInputServiceBinder; 254 } 255 256 /** 257 * Returns a new {@link android.os.Binder} 258 * 259 * <p> if an extension is provided on top of existing {@link TvInputService}; otherwise, 260 * return {@code null}. Override to provide extended interface. 261 * 262 * @see android.os.Binder#setExtension(IBinder) 263 * @hide 264 */ 265 @Nullable 266 @SystemApi createExtension()267 public IBinder createExtension() { 268 return null; 269 } 270 271 /** 272 * Returns available extension interfaces. This can be used to provide domain-specific 273 * features that are only known between certain hardware TV inputs and their clients. 274 * 275 * <p>Note that this service-level extension interface mechanism is only for hardware 276 * TV inputs that are bound even when sessions are not created. 277 * 278 * @return a non-null list of available extension interface names. An empty list 279 * indicates the TV input doesn't support any extension interfaces. 280 * @see #getExtensionInterface 281 * @see #getExtensionInterfacePermission 282 * @hide 283 */ 284 @NonNull 285 @SystemApi getAvailableExtensionInterfaceNames()286 public List<String> getAvailableExtensionInterfaceNames() { 287 return new ArrayList<>(); 288 } 289 290 /** 291 * Returns an extension interface. This can be used to provide domain-specific features 292 * that are only known between certain hardware TV inputs and their clients. 293 * 294 * <p>Note that this service-level extension interface mechanism is only for hardware 295 * TV inputs that are bound even when sessions are not created. 296 * 297 * @param name The extension interface name. 298 * @return an {@link IBinder} for the given extension interface, {@code null} if the TV input 299 * doesn't support the given extension interface. 300 * @see #getAvailableExtensionInterfaceNames 301 * @see #getExtensionInterfacePermission 302 * @hide 303 */ 304 @Nullable 305 @SystemApi getExtensionInterface(@onNull String name)306 public IBinder getExtensionInterface(@NonNull String name) { 307 return null; 308 } 309 310 /** 311 * Returns a permission for the given extension interface. This can be used to provide 312 * domain-specific features that are only known between certain hardware TV inputs and their 313 * clients. 314 * 315 * <p>Note that this service-level extension interface mechanism is only for hardware 316 * TV inputs that are bound even when sessions are not created. 317 * 318 * @param name The extension interface name. 319 * @return a name of the permission being checked for the given extension interface, 320 * {@code null} if there is no required permission, or if the TV input doesn't 321 * support the given extension interface. 322 * @see #getAvailableExtensionInterfaceNames 323 * @see #getExtensionInterface 324 * @hide 325 */ 326 @Nullable 327 @SystemApi getExtensionInterfacePermission(@onNull String name)328 public String getExtensionInterfacePermission(@NonNull String name) { 329 return null; 330 } 331 332 /** 333 * Returns a concrete implementation of {@link Session}. 334 * 335 * <p>May return {@code null} if this TV input service fails to create a session for some 336 * reason. If TV input represents an external device connected to a hardware TV input, 337 * {@link HardwareSession} should be returned. 338 * 339 * @param inputId The ID of the TV input associated with the session. 340 */ 341 @Nullable onCreateSession(@onNull String inputId)342 public abstract Session onCreateSession(@NonNull String inputId); 343 344 /** 345 * Returns a concrete implementation of {@link RecordingSession}. 346 * 347 * <p>May return {@code null} if this TV input service fails to create a recording session for 348 * some reason. 349 * 350 * @param inputId The ID of the TV input associated with the recording session. 351 */ 352 @Nullable onCreateRecordingSession(@onNull String inputId)353 public RecordingSession onCreateRecordingSession(@NonNull String inputId) { 354 return null; 355 } 356 357 /** 358 * Returns a concrete implementation of {@link Session}. 359 * 360 * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, 361 * it needs to override this method to get the sessionId passed. When no overriding, this method 362 * calls {@link #onCreateSession(String)} defaultly. 363 * 364 * @param inputId The ID of the TV input associated with the session. 365 * @param sessionId the unique sessionId created by TIF when session is created. 366 */ 367 @Nullable onCreateSession(@onNull String inputId, @NonNull String sessionId)368 public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) { 369 return onCreateSession(inputId); 370 } 371 372 /** 373 * Returns a concrete implementation of {@link RecordingSession}. 374 * 375 * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager, 376 * it needs to override this method to get the sessionId passed. When no overriding, this method 377 * calls {@link #onCreateRecordingSession(String)} defaultly. 378 * 379 * @param inputId The ID of the TV input associated with the recording session. 380 * @param sessionId the unique sessionId created by TIF when session is created. 381 */ 382 @Nullable onCreateRecordingSession( @onNull String inputId, @NonNull String sessionId)383 public RecordingSession onCreateRecordingSession( 384 @NonNull String inputId, @NonNull String sessionId) { 385 return onCreateRecordingSession(inputId); 386 } 387 388 /** 389 * Returns a new {@link TvInputInfo} object if this service is responsible for 390 * {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of 391 * ignoring all hardware input. 392 * 393 * @param hardwareInfo {@link TvInputHardwareInfo} object just added. 394 * @hide 395 */ 396 @Nullable 397 @SystemApi onHardwareAdded(TvInputHardwareInfo hardwareInfo)398 public TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) { 399 return null; 400 } 401 402 /** 403 * Returns the input ID for {@code deviceId} if it is handled by this service; 404 * otherwise, return {@code null}. Override to modify default behavior of ignoring all hardware 405 * input. 406 * 407 * @param hardwareInfo {@link TvInputHardwareInfo} object just removed. 408 * @hide 409 */ 410 @Nullable 411 @SystemApi onHardwareRemoved(TvInputHardwareInfo hardwareInfo)412 public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) { 413 return null; 414 } 415 416 /** 417 * Returns a new {@link TvInputInfo} object if this service is responsible for 418 * {@code deviceInfo}; otherwise, return {@code null}. Override to modify default behavior of 419 * ignoring all HDMI logical input device. 420 * 421 * @param deviceInfo {@link HdmiDeviceInfo} object just added. 422 * @hide 423 */ 424 @Nullable 425 @SystemApi onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo)426 public TvInputInfo onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) { 427 return null; 428 } 429 430 /** 431 * Returns the input ID for {@code deviceInfo} if it is handled by this service; otherwise, 432 * return {@code null}. Override to modify default behavior of ignoring all HDMI logical input 433 * device. 434 * 435 * @param deviceInfo {@link HdmiDeviceInfo} object just removed. 436 * @hide 437 */ 438 @Nullable 439 @SystemApi onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo)440 public String onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) { 441 return null; 442 } 443 444 /** 445 * Called when {@code deviceInfo} is updated. 446 * 447 * <p>The changes are usually cuased by the corresponding HDMI-CEC logical device. 448 * 449 * <p>The default behavior ignores all changes. 450 * 451 * <p>The TV input service responsible for {@code deviceInfo} can update the {@link TvInputInfo} 452 * object based on the updated {@code deviceInfo} (e.g. update the label based on the preferred 453 * device OSD name). 454 * 455 * @param deviceInfo the updated {@link HdmiDeviceInfo} object. 456 * @hide 457 */ 458 @SystemApi onHdmiDeviceUpdated(@onNull HdmiDeviceInfo deviceInfo)459 public void onHdmiDeviceUpdated(@NonNull HdmiDeviceInfo deviceInfo) { 460 } 461 isPassthroughInput(String inputId)462 private boolean isPassthroughInput(String inputId) { 463 if (mTvInputManager == null) { 464 mTvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); 465 } 466 TvInputInfo info = mTvInputManager.getTvInputInfo(inputId); 467 return info != null && info.isPassthroughInput(); 468 } 469 470 /** 471 * Base class for derived classes to implement to provide a TV input session. 472 */ 473 public abstract static class Session implements KeyEvent.Callback { 474 private static final int POSITION_UPDATE_INTERVAL_MS = 1000; 475 476 private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState(); 477 private final WindowManager mWindowManager; 478 final Handler mHandler; 479 private WindowManager.LayoutParams mWindowParams; 480 private Surface mSurface; 481 private final Context mContext; 482 private FrameLayout mOverlayViewContainer; 483 private View mOverlayView; 484 private OverlayViewCleanUpTask mOverlayViewCleanUpTask; 485 private boolean mOverlayViewEnabled; 486 private IBinder mWindowToken; 487 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 488 private Rect mOverlayFrame; 489 private long mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 490 private long mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 491 private final TimeShiftPositionTrackingRunnable 492 mTimeShiftPositionTrackingRunnable = new TimeShiftPositionTrackingRunnable(); 493 494 private final Object mLock = new Object(); 495 // @GuardedBy("mLock") 496 private ITvInputSessionCallback mSessionCallback; 497 // @GuardedBy("mLock") 498 private final List<Runnable> mPendingActions = new ArrayList<>(); 499 500 /** 501 * Creates a new Session. 502 * 503 * @param context The context of the application 504 */ Session(Context context)505 public Session(Context context) { 506 mContext = context; 507 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 508 mHandler = new Handler(context.getMainLooper()); 509 } 510 511 /** 512 * Enables or disables the overlay view. 513 * 514 * <p>By default, the overlay view is disabled. Must be called explicitly after the 515 * session is created to enable the overlay view. 516 * 517 * <p>The TV input service can disable its overlay view when the size of the overlay view is 518 * insufficient to display the whole information, such as when used in Picture-in-picture. 519 * Override {@link #onOverlayViewSizeChanged} to get the size of the overlay view, which 520 * then can be used to determine whether to enable/disable the overlay view. 521 * 522 * @param enable {@code true} if you want to enable the overlay view. {@code false} 523 * otherwise. 524 */ setOverlayViewEnabled(final boolean enable)525 public void setOverlayViewEnabled(final boolean enable) { 526 mHandler.post(new Runnable() { 527 @Override 528 public void run() { 529 if (enable == mOverlayViewEnabled) { 530 return; 531 } 532 mOverlayViewEnabled = enable; 533 if (enable) { 534 if (mWindowToken != null) { 535 createOverlayView(mWindowToken, mOverlayFrame); 536 } 537 } else { 538 removeOverlayView(false); 539 } 540 } 541 }); 542 } 543 544 /** 545 * Dispatches an event to the application using this session. 546 * 547 * @param eventType The type of the event. 548 * @param eventArgs Optional arguments of the event. 549 * @hide 550 */ 551 @SystemApi notifySessionEvent(@onNull final String eventType, final Bundle eventArgs)552 public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) { 553 Preconditions.checkNotNull(eventType); 554 executeOrPostRunnableOnMainThread(new Runnable() { 555 @Override 556 public void run() { 557 try { 558 if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")"); 559 if (mSessionCallback != null) { 560 mSessionCallback.onSessionEvent(eventType, eventArgs); 561 } 562 } catch (RemoteException e) { 563 Log.w(TAG, "error in sending event (event=" + eventType + ")", e); 564 } 565 } 566 }); 567 } 568 569 /** 570 * Informs the application that the current channel is re-tuned for some reason and the 571 * session now displays the content from a new channel. This is used to handle special cases 572 * such as when the current channel becomes unavailable, it is necessary to send the user to 573 * a certain channel or the user changes channel in some other way (e.g. by using a 574 * dedicated remote). 575 * 576 * @param channelUri The URI of the new channel. 577 */ notifyChannelRetuned(final Uri channelUri)578 public void notifyChannelRetuned(final Uri channelUri) { 579 executeOrPostRunnableOnMainThread(new Runnable() { 580 @MainThread 581 @Override 582 public void run() { 583 try { 584 if (DEBUG) Log.d(TAG, "notifyChannelRetuned"); 585 if (mSessionCallback != null) { 586 mSessionCallback.onChannelRetuned(channelUri); 587 } 588 } catch (RemoteException e) { 589 Log.w(TAG, "error in notifyChannelRetuned", e); 590 } 591 } 592 }); 593 } 594 595 /** 596 * Informs the application that this session has been tuned to the given channel. 597 * 598 * @param channelUri The URI of the tuned channel. 599 */ notifyTuned(@onNull Uri channelUri)600 public void notifyTuned(@NonNull Uri channelUri) { 601 executeOrPostRunnableOnMainThread(new Runnable() { 602 @MainThread 603 @Override 604 public void run() { 605 try { 606 if (DEBUG) Log.d(TAG, "notifyTuned"); 607 if (mSessionCallback != null) { 608 mSessionCallback.onTuned(channelUri); 609 } 610 } catch (RemoteException e) { 611 Log.w(TAG, "error in notifyTuned", e); 612 } 613 } 614 }); 615 } 616 617 /** 618 * Sends the list of all audio/video/subtitle tracks. The is used by the framework to 619 * maintain the track information for a given session, which in turn is used by 620 * {@link TvView#getTracks} for the application to retrieve metadata for a given track type. 621 * The TV input service must call this method as soon as the track information becomes 622 * available or is updated. Note that in a case where a part of the information for a 623 * certain track is updated, it is not necessary to create a new {@link TvTrackInfo} object 624 * with a different track ID. 625 * 626 * @param tracks A list which includes track information. 627 */ notifyTracksChanged(final List<TvTrackInfo> tracks)628 public void notifyTracksChanged(final List<TvTrackInfo> tracks) { 629 final List<TvTrackInfo> tracksCopy = new ArrayList<>(tracks); 630 executeOrPostRunnableOnMainThread(new Runnable() { 631 @MainThread 632 @Override 633 public void run() { 634 try { 635 if (DEBUG) Log.d(TAG, "notifyTracksChanged"); 636 if (mSessionCallback != null) { 637 mSessionCallback.onTracksChanged(tracksCopy); 638 } 639 } catch (RemoteException e) { 640 Log.w(TAG, "error in notifyTracksChanged", e); 641 } 642 } 643 }); 644 } 645 646 /** 647 * Sends the type and ID of a selected track. This is used to inform the application that a 648 * specific track is selected. The TV input service must call this method as soon as a track 649 * is selected either by default or in response to a call to {@link #onSelectTrack}. The 650 * selected track ID for a given type is maintained in the framework until the next call to 651 * this method even after the entire track list is updated (but is reset when the session is 652 * tuned to a new channel), so care must be taken not to result in an obsolete track ID. 653 * 654 * @param type The type of the selected track. The type can be 655 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 656 * {@link TvTrackInfo#TYPE_SUBTITLE}. 657 * @param trackId The ID of the selected track. 658 * @see #onSelectTrack 659 */ notifyTrackSelected(final int type, final String trackId)660 public void notifyTrackSelected(final int type, final String trackId) { 661 executeOrPostRunnableOnMainThread(new Runnable() { 662 @MainThread 663 @Override 664 public void run() { 665 try { 666 if (DEBUG) Log.d(TAG, "notifyTrackSelected"); 667 if (mSessionCallback != null) { 668 mSessionCallback.onTrackSelected(type, trackId); 669 } 670 } catch (RemoteException e) { 671 Log.w(TAG, "error in notifyTrackSelected", e); 672 } 673 } 674 }); 675 } 676 677 /** 678 * Informs the application that the video is now available for watching. Video is blocked 679 * until this method is called. 680 * 681 * <p>The TV input service must call this method as soon as the content rendered onto its 682 * surface is ready for viewing. This method must be called each time {@link #onTune} 683 * is called. 684 * 685 * @see #notifyVideoUnavailable 686 */ notifyVideoAvailable()687 public void notifyVideoAvailable() { 688 executeOrPostRunnableOnMainThread(new Runnable() { 689 @MainThread 690 @Override 691 public void run() { 692 try { 693 if (DEBUG) Log.d(TAG, "notifyVideoAvailable"); 694 if (mSessionCallback != null) { 695 mSessionCallback.onVideoAvailable(); 696 } 697 } catch (RemoteException e) { 698 Log.w(TAG, "error in notifyVideoAvailable", e); 699 } 700 } 701 }); 702 } 703 704 /** 705 * Informs the application that the video became unavailable for some reason. This is 706 * primarily used to signal the application to block the screen not to show any intermittent 707 * video artifacts. 708 * 709 * @param reason The reason why the video became unavailable: 710 * <ul> 711 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 712 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} 713 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 714 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 715 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY} 716 * </ul> 717 * @see #notifyVideoAvailable 718 */ notifyVideoUnavailable( @vInputManager.VideoUnavailableReason final int reason)719 public void notifyVideoUnavailable( 720 @TvInputManager.VideoUnavailableReason final int reason) { 721 if (reason < TvInputManager.VIDEO_UNAVAILABLE_REASON_START 722 || reason > TvInputManager.VIDEO_UNAVAILABLE_REASON_END) { 723 Log.e(TAG, "notifyVideoUnavailable - unknown reason: " + reason); 724 } 725 executeOrPostRunnableOnMainThread(new Runnable() { 726 @MainThread 727 @Override 728 public void run() { 729 try { 730 if (DEBUG) Log.d(TAG, "notifyVideoUnavailable"); 731 if (mSessionCallback != null) { 732 mSessionCallback.onVideoUnavailable(reason); 733 } 734 } catch (RemoteException e) { 735 Log.w(TAG, "error in notifyVideoUnavailable", e); 736 } 737 } 738 }); 739 } 740 741 /** 742 * Informs the application that the user is allowed to watch the current program content. 743 * 744 * <p>Each TV input service is required to query the system whether the user is allowed to 745 * watch the current program before showing it to the user if the parental controls is 746 * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled 747 * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input 748 * service should block the content or not is determined by invoking 749 * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)} 750 * with the content rating for the current program. Then the {@link TvInputManager} makes a 751 * judgment based on the user blocked ratings stored in the secure settings and returns the 752 * result. If the rating in question turns out to be allowed by the user, the TV input 753 * service must call this method to notify the application that is permitted to show the 754 * content. 755 * 756 * <p>Each TV input service also needs to continuously listen to any changes made to the 757 * parental controls settings by registering a broadcast receiver to receive 758 * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and 759 * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately 760 * reevaluate the current program with the new parental controls settings. 761 * 762 * @see #notifyContentBlocked 763 * @see TvInputManager 764 */ notifyContentAllowed()765 public void notifyContentAllowed() { 766 executeOrPostRunnableOnMainThread(new Runnable() { 767 @MainThread 768 @Override 769 public void run() { 770 try { 771 if (DEBUG) Log.d(TAG, "notifyContentAllowed"); 772 if (mSessionCallback != null) { 773 mSessionCallback.onContentAllowed(); 774 } 775 } catch (RemoteException e) { 776 Log.w(TAG, "error in notifyContentAllowed", e); 777 } 778 } 779 }); 780 } 781 782 /** 783 * Informs the application that the current program content is blocked by parent controls. 784 * 785 * <p>Each TV input service is required to query the system whether the user is allowed to 786 * watch the current program before showing it to the user if the parental controls is 787 * enabled (i.e. {@link TvInputManager#isParentalControlsEnabled 788 * TvInputManager.isParentalControlsEnabled()} returns {@code true}). Whether the TV input 789 * service should block the content or not is determined by invoking 790 * {@link TvInputManager#isRatingBlocked TvInputManager.isRatingBlocked(TvContentRating)} 791 * with the content rating for the current program or {@link TvContentRating#UNRATED} in 792 * case the rating information is missing. Then the {@link TvInputManager} makes a judgment 793 * based on the user blocked ratings stored in the secure settings and returns the result. 794 * If the rating in question turns out to be blocked, the TV input service must immediately 795 * block the content and call this method with the content rating of the current program to 796 * prompt the PIN verification screen. 797 * 798 * <p>Each TV input service also needs to continuously listen to any changes made to the 799 * parental controls settings by registering a broadcast receiver to receive 800 * {@link TvInputManager#ACTION_BLOCKED_RATINGS_CHANGED} and 801 * {@link TvInputManager#ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED} and immediately 802 * reevaluate the current program with the new parental controls settings. 803 * 804 * @param rating The content rating for the current TV program. Can be 805 * {@link TvContentRating#UNRATED}. 806 * @see #notifyContentAllowed 807 * @see TvInputManager 808 */ notifyContentBlocked(@onNull final TvContentRating rating)809 public void notifyContentBlocked(@NonNull final TvContentRating rating) { 810 Preconditions.checkNotNull(rating); 811 executeOrPostRunnableOnMainThread(new Runnable() { 812 @MainThread 813 @Override 814 public void run() { 815 try { 816 if (DEBUG) Log.d(TAG, "notifyContentBlocked"); 817 if (mSessionCallback != null) { 818 mSessionCallback.onContentBlocked(rating.flattenToString()); 819 } 820 } catch (RemoteException e) { 821 Log.w(TAG, "error in notifyContentBlocked", e); 822 } 823 } 824 }); 825 } 826 827 /** 828 * Informs the application that the time shift status is changed. 829 * 830 * <p>Prior to calling this method, the application assumes the status 831 * {@link TvInputManager#TIME_SHIFT_STATUS_UNKNOWN}. Right after the session is created, it 832 * is important to invoke the method with the status 833 * {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} if the implementation does support 834 * time shifting, or {@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} otherwise. Failure 835 * to notifying the current status change immediately might result in an undesirable 836 * behavior in the application such as hiding the play controls. 837 * 838 * <p>If the status {@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} is reported, the 839 * application assumes it can pause/resume playback, seek to a specified time position and 840 * set playback rate and audio mode. The implementation should override 841 * {@link #onTimeShiftPause}, {@link #onTimeShiftResume}, {@link #onTimeShiftSeekTo}, 842 * {@link #onTimeShiftGetStartPosition}, {@link #onTimeShiftGetCurrentPosition} and 843 * {@link #onTimeShiftSetPlaybackParams}. 844 * 845 * @param status The current time shift status. Should be one of the followings. 846 * <ul> 847 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} 848 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} 849 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} 850 * </ul> 851 */ notifyTimeShiftStatusChanged(@vInputManager.TimeShiftStatus final int status)852 public void notifyTimeShiftStatusChanged(@TvInputManager.TimeShiftStatus final int status) { 853 executeOrPostRunnableOnMainThread(new Runnable() { 854 @MainThread 855 @Override 856 public void run() { 857 timeShiftEnablePositionTracking( 858 status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE); 859 try { 860 if (DEBUG) Log.d(TAG, "notifyTimeShiftStatusChanged"); 861 if (mSessionCallback != null) { 862 mSessionCallback.onTimeShiftStatusChanged(status); 863 } 864 } catch (RemoteException e) { 865 Log.w(TAG, "error in notifyTimeShiftStatusChanged", e); 866 } 867 } 868 }); 869 } 870 871 /** 872 * Notifies response for broadcast info. 873 * 874 * @param response broadcast info response. 875 */ notifyBroadcastInfoResponse(@onNull final BroadcastInfoResponse response)876 public void notifyBroadcastInfoResponse(@NonNull final BroadcastInfoResponse response) { 877 executeOrPostRunnableOnMainThread(new Runnable() { 878 @MainThread 879 @Override 880 public void run() { 881 try { 882 if (DEBUG) Log.d(TAG, "notifyBroadcastInfoResponse"); 883 if (mSessionCallback != null) { 884 mSessionCallback.onBroadcastInfoResponse(response); 885 } 886 } catch (RemoteException e) { 887 Log.w(TAG, "error in notifyBroadcastInfoResponse", e); 888 } 889 } 890 }); 891 } 892 893 /** 894 * Notifies response for advertisement. 895 * 896 * @param response advertisement response. 897 * @see android.media.tv.interactive.TvInteractiveAppService.Session#requestAd(AdRequest) 898 */ notifyAdResponse(@onNull final AdResponse response)899 public void notifyAdResponse(@NonNull final AdResponse response) { 900 executeOrPostRunnableOnMainThread(new Runnable() { 901 @MainThread 902 @Override 903 public void run() { 904 try { 905 if (DEBUG) Log.d(TAG, "notifyAdResponse"); 906 if (mSessionCallback != null) { 907 mSessionCallback.onAdResponse(response); 908 } 909 } catch (RemoteException e) { 910 Log.w(TAG, "error in notifyAdResponse", e); 911 } 912 } 913 }); 914 } 915 notifyTimeShiftStartPositionChanged(final long timeMs)916 private void notifyTimeShiftStartPositionChanged(final long timeMs) { 917 executeOrPostRunnableOnMainThread(new Runnable() { 918 @MainThread 919 @Override 920 public void run() { 921 try { 922 if (DEBUG) Log.d(TAG, "notifyTimeShiftStartPositionChanged"); 923 if (mSessionCallback != null) { 924 mSessionCallback.onTimeShiftStartPositionChanged(timeMs); 925 } 926 } catch (RemoteException e) { 927 Log.w(TAG, "error in notifyTimeShiftStartPositionChanged", e); 928 } 929 } 930 }); 931 } 932 notifyTimeShiftCurrentPositionChanged(final long timeMs)933 private void notifyTimeShiftCurrentPositionChanged(final long timeMs) { 934 executeOrPostRunnableOnMainThread(new Runnable() { 935 @MainThread 936 @Override 937 public void run() { 938 try { 939 if (DEBUG) Log.d(TAG, "notifyTimeShiftCurrentPositionChanged"); 940 if (mSessionCallback != null) { 941 mSessionCallback.onTimeShiftCurrentPositionChanged(timeMs); 942 } 943 } catch (RemoteException e) { 944 Log.w(TAG, "error in notifyTimeShiftCurrentPositionChanged", e); 945 } 946 } 947 }); 948 } 949 950 /** 951 * Informs the app that the AIT (Application Information Table) is updated. 952 * 953 * <p>This method should also be called when 954 * {@link #onSetInteractiveAppNotificationEnabled(boolean)} is called to send the first AIT 955 * info. 956 * 957 * @see #onSetInteractiveAppNotificationEnabled(boolean) 958 */ notifyAitInfoUpdated(@onNull final AitInfo aitInfo)959 public void notifyAitInfoUpdated(@NonNull final AitInfo aitInfo) { 960 executeOrPostRunnableOnMainThread(new Runnable() { 961 @MainThread 962 @Override 963 public void run() { 964 try { 965 if (DEBUG) Log.d(TAG, "notifyAitInfoUpdated"); 966 if (mSessionCallback != null) { 967 mSessionCallback.onAitInfoUpdated(aitInfo); 968 } 969 } catch (RemoteException e) { 970 Log.w(TAG, "error in notifyAitInfoUpdated", e); 971 } 972 } 973 }); 974 } 975 976 /** 977 * Notifies signal strength. 978 */ notifySignalStrength(@vInputManager.SignalStrength final int strength)979 public void notifySignalStrength(@TvInputManager.SignalStrength final int strength) { 980 executeOrPostRunnableOnMainThread(new Runnable() { 981 @MainThread 982 @Override 983 public void run() { 984 try { 985 if (DEBUG) Log.d(TAG, "notifySignalStrength"); 986 if (mSessionCallback != null) { 987 mSessionCallback.onSignalStrength(strength); 988 } 989 } catch (RemoteException e) { 990 Log.w(TAG, "error in notifySignalStrength", e); 991 } 992 } 993 }); 994 } 995 996 /** 997 * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position 998 * is relative to the overlay view that sits on top of this surface. 999 * 1000 * @param left Left position in pixels, relative to the overlay view. 1001 * @param top Top position in pixels, relative to the overlay view. 1002 * @param right Right position in pixels, relative to the overlay view. 1003 * @param bottom Bottom position in pixels, relative to the overlay view. 1004 * @see #onOverlayViewSizeChanged 1005 */ layoutSurface(final int left, final int top, final int right, final int bottom)1006 public void layoutSurface(final int left, final int top, final int right, 1007 final int bottom) { 1008 if (left > right || top > bottom) { 1009 throw new IllegalArgumentException("Invalid parameter"); 1010 } 1011 executeOrPostRunnableOnMainThread(new Runnable() { 1012 @MainThread 1013 @Override 1014 public void run() { 1015 try { 1016 if (DEBUG) Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top + ", r=" 1017 + right + ", b=" + bottom + ",)"); 1018 if (mSessionCallback != null) { 1019 mSessionCallback.onLayoutSurface(left, top, right, bottom); 1020 } 1021 } catch (RemoteException e) { 1022 Log.w(TAG, "error in layoutSurface", e); 1023 } 1024 } 1025 }); 1026 } 1027 1028 /** 1029 * Called when the session is released. 1030 */ onRelease()1031 public abstract void onRelease(); 1032 1033 /** 1034 * Sets the current session as the main session. The main session is a session whose 1035 * corresponding TV input determines the HDMI-CEC active source device. 1036 * 1037 * <p>TV input service that manages HDMI-CEC logical device should implement {@link 1038 * #onSetMain} to (1) select the corresponding HDMI logical device as the source device 1039 * when {@code isMain} is {@code true}, and to (2) select the internal device (= TV itself) 1040 * as the source device when {@code isMain} is {@code false} and the session is still main. 1041 * Also, if a surface is passed to a non-main session and active source is changed to 1042 * initiate the surface, the active source should be returned to the main session. 1043 * 1044 * <p>{@link TvView} guarantees that, when tuning involves a session transition, {@code 1045 * onSetMain(true)} for new session is called first, {@code onSetMain(false)} for old 1046 * session is called afterwards. This allows {@code onSetMain(false)} to be no-op when TV 1047 * input service knows that the next main session corresponds to another HDMI logical 1048 * device. Practically, this implies that one TV input service should handle all HDMI port 1049 * and HDMI-CEC logical devices for smooth active source transition. 1050 * 1051 * @param isMain If true, session should become main. 1052 * @see TvView#setMain 1053 * @hide 1054 */ 1055 @SystemApi onSetMain(boolean isMain)1056 public void onSetMain(boolean isMain) { 1057 } 1058 1059 /** 1060 * Called when the application sets the surface. 1061 * 1062 * <p>The TV input service should render video onto the given surface. When called with 1063 * {@code null}, the input service should immediately free any references to the 1064 * currently set surface and stop using it. 1065 * 1066 * @param surface The surface to be used for video rendering. Can be {@code null}. 1067 * @return {@code true} if the surface was set successfully, {@code false} otherwise. 1068 */ onSetSurface(@ullable Surface surface)1069 public abstract boolean onSetSurface(@Nullable Surface surface); 1070 1071 /** 1072 * Called after any structural changes (format or size) have been made to the surface passed 1073 * in {@link #onSetSurface}. This method is always called at least once, after 1074 * {@link #onSetSurface} is called with non-null surface. 1075 * 1076 * @param format The new PixelFormat of the surface. 1077 * @param width The new width of the surface. 1078 * @param height The new height of the surface. 1079 */ onSurfaceChanged(int format, int width, int height)1080 public void onSurfaceChanged(int format, int width, int height) { 1081 } 1082 1083 /** 1084 * Called when the size of the overlay view is changed by the application. 1085 * 1086 * <p>This is always called at least once when the session is created regardless of whether 1087 * the overlay view is enabled or not. The overlay view size is the same as the containing 1088 * {@link TvView}. Note that the size of the underlying surface can be different if the 1089 * surface was changed by calling {@link #layoutSurface}. 1090 * 1091 * @param width The width of the overlay view. 1092 * @param height The height of the overlay view. 1093 */ onOverlayViewSizeChanged(int width, int height)1094 public void onOverlayViewSizeChanged(int width, int height) { 1095 } 1096 1097 /** 1098 * Sets the relative stream volume of the current TV input session. 1099 * 1100 * <p>The implementation should honor this request in order to handle audio focus changes or 1101 * mute the current session when multiple sessions, possibly from different inputs are 1102 * active. If the method has not yet been called, the implementation should assume the 1103 * default value of {@code 1.0f}. 1104 * 1105 * @param volume A volume value between {@code 0.0f} to {@code 1.0f}. 1106 */ onSetStreamVolume(@loatRangefrom = 0.0, to = 1.0) float volume)1107 public abstract void onSetStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume); 1108 1109 /** 1110 * called when broadcast info is requested. 1111 * 1112 * @param request broadcast info request 1113 */ onRequestBroadcastInfo(@onNull BroadcastInfoRequest request)1114 public void onRequestBroadcastInfo(@NonNull BroadcastInfoRequest request) { 1115 } 1116 1117 /** 1118 * called when broadcast info is removed. 1119 */ onRemoveBroadcastInfo(int requestId)1120 public void onRemoveBroadcastInfo(int requestId) { 1121 } 1122 1123 /** 1124 * Called when advertisement request is received. 1125 * 1126 * @param request advertisement request received 1127 */ onRequestAd(@onNull AdRequest request)1128 public void onRequestAd(@NonNull AdRequest request) { 1129 } 1130 1131 /** 1132 * Tunes to a given channel. 1133 * 1134 * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called. 1135 * Also, {@link #notifyVideoUnavailable(int)} should be called when the TV input cannot 1136 * continue playing the given channel. 1137 * 1138 * @param channelUri The URI of the channel. 1139 * @return {@code true} if the tuning was successful, {@code false} otherwise. 1140 */ onTune(Uri channelUri)1141 public abstract boolean onTune(Uri channelUri); 1142 1143 /** 1144 * Tunes to a given channel. Override this method in order to handle domain-specific 1145 * features that are only known between certain TV inputs and their clients. 1146 * 1147 * <p>The default implementation calls {@link #onTune(Uri)}. 1148 * 1149 * @param channelUri The URI of the channel. 1150 * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped 1151 * name, i.e. prefixed with a package name you own, so that different developers 1152 * will not create conflicting keys. 1153 * @return {@code true} if the tuning was successful, {@code false} otherwise. 1154 */ onTune(Uri channelUri, Bundle params)1155 public boolean onTune(Uri channelUri, Bundle params) { 1156 return onTune(channelUri); 1157 } 1158 1159 /** 1160 * Enables or disables the caption. 1161 * 1162 * <p>The locale for the user's preferred captioning language can be obtained by calling 1163 * {@link CaptioningManager#getLocale CaptioningManager.getLocale()}. 1164 * 1165 * @param enabled {@code true} to enable, {@code false} to disable. 1166 * @see CaptioningManager 1167 */ onSetCaptionEnabled(boolean enabled)1168 public abstract void onSetCaptionEnabled(boolean enabled); 1169 1170 /** 1171 * Requests to unblock the content according to the given rating. 1172 * 1173 * <p>The implementation should unblock the content. 1174 * TV input service has responsibility to decide when/how the unblock expires 1175 * while it can keep previously unblocked ratings in order not to ask a user 1176 * to unblock whenever a content rating is changed. 1177 * Therefore an unblocked rating can be valid for a channel, a program, 1178 * or certain amount of time depending on the implementation. 1179 * 1180 * @param unblockedRating An unblocked content rating 1181 */ onUnblockContent(TvContentRating unblockedRating)1182 public void onUnblockContent(TvContentRating unblockedRating) { 1183 } 1184 1185 /** 1186 * Selects a given track. 1187 * 1188 * <p>If this is done successfully, the implementation should call 1189 * {@link #notifyTrackSelected} to help applications maintain the up-to-date list of the 1190 * selected tracks. 1191 * 1192 * @param trackId The ID of the track to select. {@code null} means to unselect the current 1193 * track for a given type. 1194 * @param type The type of the track to select. The type can be 1195 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 1196 * {@link TvTrackInfo#TYPE_SUBTITLE}. 1197 * @return {@code true} if the track selection was successful, {@code false} otherwise. 1198 * @see #notifyTrackSelected 1199 */ onSelectTrack(int type, @Nullable String trackId)1200 public boolean onSelectTrack(int type, @Nullable String trackId) { 1201 return false; 1202 } 1203 1204 /** 1205 * Enables or disables interactive app notification. 1206 * 1207 * <p>This method enables or disables the event detection from the corresponding TV input. 1208 * When it's enabled, the TV input service detects events related to interactive app, such 1209 * as AIT (Application Information Table) and sends to TvView or the linked TV interactive 1210 * app service. 1211 * 1212 * @param enabled {@code true} to enable, {@code false} to disable. 1213 * 1214 * @see TvView#setInteractiveAppNotificationEnabled(boolean) 1215 * @see Session#notifyAitInfoUpdated(android.media.tv.AitInfo) 1216 */ onSetInteractiveAppNotificationEnabled(boolean enabled)1217 public void onSetInteractiveAppNotificationEnabled(boolean enabled) { 1218 } 1219 1220 /** 1221 * Processes a private command sent from the application to the TV input. This can be used 1222 * to provide domain-specific features that are only known between certain TV inputs and 1223 * their clients. 1224 * 1225 * @param action Name of the command to be performed. This <em>must</em> be a scoped name, 1226 * i.e. prefixed with a package name you own, so that different developers will 1227 * not create conflicting commands. 1228 * @param data Any data to include with the command. 1229 */ onAppPrivateCommand(@onNull String action, Bundle data)1230 public void onAppPrivateCommand(@NonNull String action, Bundle data) { 1231 } 1232 1233 /** 1234 * Called when the application requests to create an overlay view. Each session 1235 * implementation can override this method and return its own view. 1236 * 1237 * @return a view attached to the overlay window 1238 */ onCreateOverlayView()1239 public View onCreateOverlayView() { 1240 return null; 1241 } 1242 1243 /** 1244 * Called when the application requests to play a given recorded TV program. 1245 * 1246 * @param recordedProgramUri The URI of a recorded TV program. 1247 * @see #onTimeShiftResume() 1248 * @see #onTimeShiftPause() 1249 * @see #onTimeShiftSeekTo(long) 1250 * @see #onTimeShiftSetPlaybackParams(PlaybackParams) 1251 * @see #onTimeShiftGetStartPosition() 1252 * @see #onTimeShiftGetCurrentPosition() 1253 */ onTimeShiftPlay(Uri recordedProgramUri)1254 public void onTimeShiftPlay(Uri recordedProgramUri) { 1255 } 1256 1257 /** 1258 * Called when the application requests to pause playback. 1259 * 1260 * @see #onTimeShiftPlay(Uri) 1261 * @see #onTimeShiftResume() 1262 * @see #onTimeShiftSeekTo(long) 1263 * @see #onTimeShiftSetPlaybackParams(PlaybackParams) 1264 * @see #onTimeShiftGetStartPosition() 1265 * @see #onTimeShiftGetCurrentPosition() 1266 */ onTimeShiftPause()1267 public void onTimeShiftPause() { 1268 } 1269 1270 /** 1271 * Called when the application requests to resume playback. 1272 * 1273 * @see #onTimeShiftPlay(Uri) 1274 * @see #onTimeShiftPause() 1275 * @see #onTimeShiftSeekTo(long) 1276 * @see #onTimeShiftSetPlaybackParams(PlaybackParams) 1277 * @see #onTimeShiftGetStartPosition() 1278 * @see #onTimeShiftGetCurrentPosition() 1279 */ onTimeShiftResume()1280 public void onTimeShiftResume() { 1281 } 1282 1283 /** 1284 * Called when the application requests to seek to a specified time position. Normally, the 1285 * position is given within range between the start and the current time, inclusively. The 1286 * implementation is expected to seek to the nearest time position if the given position is 1287 * not in the range. 1288 * 1289 * @param timeMs The time position to seek to, in milliseconds since the epoch. 1290 * @see #onTimeShiftPlay(Uri) 1291 * @see #onTimeShiftResume() 1292 * @see #onTimeShiftPause() 1293 * @see #onTimeShiftSetPlaybackParams(PlaybackParams) 1294 * @see #onTimeShiftGetStartPosition() 1295 * @see #onTimeShiftGetCurrentPosition() 1296 */ onTimeShiftSeekTo(long timeMs)1297 public void onTimeShiftSeekTo(long timeMs) { 1298 } 1299 1300 /** 1301 * Called when the application sets playback parameters containing the speed and audio mode. 1302 * 1303 * <p>Once the playback parameters are set, the implementation should honor the current 1304 * settings until the next tune request. Pause/resume/seek request does not reset the 1305 * parameters previously set. 1306 * 1307 * @param params The playback params. 1308 * @see #onTimeShiftPlay(Uri) 1309 * @see #onTimeShiftResume() 1310 * @see #onTimeShiftPause() 1311 * @see #onTimeShiftSeekTo(long) 1312 * @see #onTimeShiftGetStartPosition() 1313 * @see #onTimeShiftGetCurrentPosition() 1314 */ onTimeShiftSetPlaybackParams(PlaybackParams params)1315 public void onTimeShiftSetPlaybackParams(PlaybackParams params) { 1316 } 1317 1318 /** 1319 * Returns the start position for time shifting, in milliseconds since the epoch. 1320 * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the 1321 * moment. 1322 * 1323 * <p>The start position for time shifting indicates the earliest possible time the user can 1324 * seek to. Initially this is equivalent to the time when the implementation starts 1325 * recording. Later it may be adjusted because there is insufficient space or the duration 1326 * of recording is limited by the implementation. The application does not allow the user to 1327 * seek to a position earlier than the start position. 1328 * 1329 * <p>For playback of a recorded program initiated by {@link #onTimeShiftPlay(Uri)}, the 1330 * start position should be 0 and does not change. 1331 * 1332 * @see #onTimeShiftPlay(Uri) 1333 * @see #onTimeShiftResume() 1334 * @see #onTimeShiftPause() 1335 * @see #onTimeShiftSeekTo(long) 1336 * @see #onTimeShiftSetPlaybackParams(PlaybackParams) 1337 * @see #onTimeShiftGetCurrentPosition() 1338 */ onTimeShiftGetStartPosition()1339 public long onTimeShiftGetStartPosition() { 1340 return TvInputManager.TIME_SHIFT_INVALID_TIME; 1341 } 1342 1343 /** 1344 * Returns the current position for time shifting, in milliseconds since the epoch. 1345 * Returns {@link TvInputManager#TIME_SHIFT_INVALID_TIME} if the position is unknown at the 1346 * moment. 1347 * 1348 * <p>The current position for time shifting is the same as the current position of 1349 * playback. It should be equal to or greater than the start position reported by 1350 * {@link #onTimeShiftGetStartPosition()}. When playback is completed, the current position 1351 * should stay where the playback ends, in other words, the returned value of this mehtod 1352 * should be equal to the start position plus the duration of the program. 1353 * 1354 * @see #onTimeShiftPlay(Uri) 1355 * @see #onTimeShiftResume() 1356 * @see #onTimeShiftPause() 1357 * @see #onTimeShiftSeekTo(long) 1358 * @see #onTimeShiftSetPlaybackParams(PlaybackParams) 1359 * @see #onTimeShiftGetStartPosition() 1360 */ onTimeShiftGetCurrentPosition()1361 public long onTimeShiftGetCurrentPosition() { 1362 return TvInputManager.TIME_SHIFT_INVALID_TIME; 1363 } 1364 1365 /** 1366 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyDown(int, KeyEvent) 1367 * KeyEvent.Callback.onKeyDown()}: always returns false (doesn't handle the event). 1368 * 1369 * <p>Override this to intercept key down events before they are processed by the 1370 * application. If you return true, the application will not process the event itself. If 1371 * you return false, the normal application processing will occur as if the TV input had not 1372 * seen the event at all. 1373 * 1374 * @param keyCode The value in event.getKeyCode(). 1375 * @param event Description of the key event. 1376 * @return If you handled the event, return {@code true}. If you want to allow the event to 1377 * be handled by the next receiver, return {@code false}. 1378 */ 1379 @Override onKeyDown(int keyCode, KeyEvent event)1380 public boolean onKeyDown(int keyCode, KeyEvent event) { 1381 return false; 1382 } 1383 1384 /** 1385 * Default implementation of 1386 * {@link android.view.KeyEvent.Callback#onKeyLongPress(int, KeyEvent) 1387 * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle the event). 1388 * 1389 * <p>Override this to intercept key long press events before they are processed by the 1390 * application. If you return true, the application will not process the event itself. If 1391 * you return false, the normal application processing will occur as if the TV input had not 1392 * seen the event at all. 1393 * 1394 * @param keyCode The value in event.getKeyCode(). 1395 * @param event Description of the key event. 1396 * @return If you handled the event, return {@code true}. If you want to allow the event to 1397 * be handled by the next receiver, return {@code false}. 1398 */ 1399 @Override onKeyLongPress(int keyCode, KeyEvent event)1400 public boolean onKeyLongPress(int keyCode, KeyEvent event) { 1401 return false; 1402 } 1403 1404 /** 1405 * Default implementation of 1406 * {@link android.view.KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) 1407 * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle the event). 1408 * 1409 * <p>Override this to intercept special key multiple events before they are processed by 1410 * the application. If you return true, the application will not itself process the event. 1411 * If you return false, the normal application processing will occur as if the TV input had 1412 * not seen the event at all. 1413 * 1414 * @param keyCode The value in event.getKeyCode(). 1415 * @param count The number of times the action was made. 1416 * @param event Description of the key event. 1417 * @return If you handled the event, return {@code true}. If you want to allow the event to 1418 * be handled by the next receiver, return {@code false}. 1419 */ 1420 @Override onKeyMultiple(int keyCode, int count, KeyEvent event)1421 public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) { 1422 return false; 1423 } 1424 1425 /** 1426 * Default implementation of {@link android.view.KeyEvent.Callback#onKeyUp(int, KeyEvent) 1427 * KeyEvent.Callback.onKeyUp()}: always returns false (doesn't handle the event). 1428 * 1429 * <p>Override this to intercept key up events before they are processed by the application. 1430 * If you return true, the application will not itself process the event. If you return false, 1431 * the normal application processing will occur as if the TV input had not seen the event at 1432 * all. 1433 * 1434 * @param keyCode The value in event.getKeyCode(). 1435 * @param event Description of the key event. 1436 * @return If you handled the event, return {@code true}. If you want to allow the event to 1437 * be handled by the next receiver, return {@code false}. 1438 */ 1439 @Override onKeyUp(int keyCode, KeyEvent event)1440 public boolean onKeyUp(int keyCode, KeyEvent event) { 1441 return false; 1442 } 1443 1444 /** 1445 * Implement this method to handle touch screen motion events on the current input session. 1446 * 1447 * @param event The motion event being received. 1448 * @return If you handled the event, return {@code true}. If you want to allow the event to 1449 * be handled by the next receiver, return {@code false}. 1450 * @see View#onTouchEvent 1451 */ onTouchEvent(MotionEvent event)1452 public boolean onTouchEvent(MotionEvent event) { 1453 return false; 1454 } 1455 1456 /** 1457 * Implement this method to handle trackball events on the current input session. 1458 * 1459 * @param event The motion event being received. 1460 * @return If you handled the event, return {@code true}. If you want to allow the event to 1461 * be handled by the next receiver, return {@code false}. 1462 * @see View#onTrackballEvent 1463 */ onTrackballEvent(MotionEvent event)1464 public boolean onTrackballEvent(MotionEvent event) { 1465 return false; 1466 } 1467 1468 /** 1469 * Implement this method to handle generic motion events on the current input session. 1470 * 1471 * @param event The motion event being received. 1472 * @return If you handled the event, return {@code true}. If you want to allow the event to 1473 * be handled by the next receiver, return {@code false}. 1474 * @see View#onGenericMotionEvent 1475 */ onGenericMotionEvent(MotionEvent event)1476 public boolean onGenericMotionEvent(MotionEvent event) { 1477 return false; 1478 } 1479 1480 /** 1481 * This method is called when the application would like to stop using the current input 1482 * session. 1483 */ release()1484 void release() { 1485 onRelease(); 1486 if (mSurface != null) { 1487 mSurface.release(); 1488 mSurface = null; 1489 } 1490 synchronized(mLock) { 1491 mSessionCallback = null; 1492 mPendingActions.clear(); 1493 } 1494 // Removes the overlay view lastly so that any hanging on the main thread can be handled 1495 // in {@link #scheduleOverlayViewCleanup}. 1496 removeOverlayView(true); 1497 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable); 1498 } 1499 1500 /** 1501 * Calls {@link #onSetMain}. 1502 */ setMain(boolean isMain)1503 void setMain(boolean isMain) { 1504 onSetMain(isMain); 1505 } 1506 1507 /** 1508 * Calls {@link #onSetSurface}. 1509 */ setSurface(Surface surface)1510 void setSurface(Surface surface) { 1511 onSetSurface(surface); 1512 if (mSurface != null) { 1513 mSurface.release(); 1514 } 1515 mSurface = surface; 1516 // TODO: Handle failure. 1517 } 1518 1519 /** 1520 * Calls {@link #onSurfaceChanged}. 1521 */ dispatchSurfaceChanged(int format, int width, int height)1522 void dispatchSurfaceChanged(int format, int width, int height) { 1523 if (DEBUG) { 1524 Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width 1525 + ", height=" + height + ")"); 1526 } 1527 onSurfaceChanged(format, width, height); 1528 } 1529 1530 /** 1531 * Calls {@link #onSetStreamVolume}. 1532 */ setStreamVolume(float volume)1533 void setStreamVolume(float volume) { 1534 onSetStreamVolume(volume); 1535 } 1536 1537 /** 1538 * Calls {@link #onTune(Uri, Bundle)}. 1539 */ tune(Uri channelUri, Bundle params)1540 void tune(Uri channelUri, Bundle params) { 1541 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 1542 onTune(channelUri, params); 1543 // TODO: Handle failure. 1544 } 1545 1546 /** 1547 * Calls {@link #onSetCaptionEnabled}. 1548 */ setCaptionEnabled(boolean enabled)1549 void setCaptionEnabled(boolean enabled) { 1550 onSetCaptionEnabled(enabled); 1551 } 1552 1553 /** 1554 * Calls {@link #onSelectTrack}. 1555 */ selectTrack(int type, String trackId)1556 void selectTrack(int type, String trackId) { 1557 onSelectTrack(type, trackId); 1558 } 1559 1560 /** 1561 * Calls {@link #onUnblockContent}. 1562 */ unblockContent(String unblockedRating)1563 void unblockContent(String unblockedRating) { 1564 onUnblockContent(TvContentRating.unflattenFromString(unblockedRating)); 1565 // TODO: Handle failure. 1566 } 1567 1568 /** 1569 * Calls {@link #onSetInteractiveAppNotificationEnabled}. 1570 */ setInteractiveAppNotificationEnabled(boolean enabled)1571 void setInteractiveAppNotificationEnabled(boolean enabled) { 1572 onSetInteractiveAppNotificationEnabled(enabled); 1573 } 1574 1575 /** 1576 * Calls {@link #onAppPrivateCommand}. 1577 */ appPrivateCommand(String action, Bundle data)1578 void appPrivateCommand(String action, Bundle data) { 1579 onAppPrivateCommand(action, data); 1580 } 1581 1582 /** 1583 * Creates an overlay view. This calls {@link #onCreateOverlayView} to get a view to attach 1584 * to the overlay window. 1585 * 1586 * @param windowToken A window token of the application. 1587 * @param frame A position of the overlay view. 1588 */ createOverlayView(IBinder windowToken, Rect frame)1589 void createOverlayView(IBinder windowToken, Rect frame) { 1590 if (mOverlayViewContainer != null) { 1591 removeOverlayView(false); 1592 } 1593 if (DEBUG) Log.d(TAG, "create overlay view(" + frame + ")"); 1594 mWindowToken = windowToken; 1595 mOverlayFrame = frame; 1596 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top); 1597 if (!mOverlayViewEnabled) { 1598 return; 1599 } 1600 mOverlayView = onCreateOverlayView(); 1601 if (mOverlayView == null) { 1602 return; 1603 } 1604 if (mOverlayViewCleanUpTask != null) { 1605 mOverlayViewCleanUpTask.cancel(true); 1606 mOverlayViewCleanUpTask = null; 1607 } 1608 // Creates a container view to check hanging on the overlay view detaching. 1609 // Adding/removing the overlay view to/from the container make the view attach/detach 1610 // logic run on the main thread. 1611 mOverlayViewContainer = new FrameLayout(mContext.getApplicationContext()); 1612 mOverlayViewContainer.addView(mOverlayView); 1613 // TvView's window type is TYPE_APPLICATION_MEDIA and we want to create 1614 // an overlay window above the media window but below the application window. 1615 int type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY; 1616 // We make the overlay view non-focusable and non-touchable so that 1617 // the application that owns the window token can decide whether to consume or 1618 // dispatch the input events. 1619 int flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 1620 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE 1621 | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; 1622 if (ActivityManager.isHighEndGfx()) { 1623 flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 1624 } 1625 mWindowParams = new WindowManager.LayoutParams( 1626 frame.right - frame.left, frame.bottom - frame.top, 1627 frame.left, frame.top, type, flags, PixelFormat.TRANSPARENT); 1628 mWindowParams.privateFlags |= 1629 WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; 1630 mWindowParams.gravity = Gravity.START | Gravity.TOP; 1631 mWindowParams.token = windowToken; 1632 mWindowManager.addView(mOverlayViewContainer, mWindowParams); 1633 } 1634 1635 /** 1636 * Relayouts the current overlay view. 1637 * 1638 * @param frame A new position of the overlay view. 1639 */ relayoutOverlayView(Rect frame)1640 void relayoutOverlayView(Rect frame) { 1641 if (DEBUG) Log.d(TAG, "relayoutOverlayView(" + frame + ")"); 1642 if (mOverlayFrame == null || mOverlayFrame.width() != frame.width() 1643 || mOverlayFrame.height() != frame.height()) { 1644 // Note: relayoutOverlayView is called whenever TvView's layout is changed 1645 // regardless of setOverlayViewEnabled. 1646 onOverlayViewSizeChanged(frame.right - frame.left, frame.bottom - frame.top); 1647 } 1648 mOverlayFrame = frame; 1649 if (!mOverlayViewEnabled || mOverlayViewContainer == null) { 1650 return; 1651 } 1652 mWindowParams.x = frame.left; 1653 mWindowParams.y = frame.top; 1654 mWindowParams.width = frame.right - frame.left; 1655 mWindowParams.height = frame.bottom - frame.top; 1656 mWindowManager.updateViewLayout(mOverlayViewContainer, mWindowParams); 1657 } 1658 1659 /** 1660 * Removes the current overlay view. 1661 */ removeOverlayView(boolean clearWindowToken)1662 void removeOverlayView(boolean clearWindowToken) { 1663 if (DEBUG) Log.d(TAG, "removeOverlayView(" + mOverlayViewContainer + ")"); 1664 if (clearWindowToken) { 1665 mWindowToken = null; 1666 mOverlayFrame = null; 1667 } 1668 if (mOverlayViewContainer != null) { 1669 // Removes the overlay view from the view hierarchy in advance so that it can be 1670 // cleaned up in the {@link OverlayViewCleanUpTask} if the remove process is 1671 // hanging. 1672 mOverlayViewContainer.removeView(mOverlayView); 1673 mOverlayView = null; 1674 mWindowManager.removeView(mOverlayViewContainer); 1675 mOverlayViewContainer = null; 1676 mWindowParams = null; 1677 } 1678 } 1679 1680 /** 1681 * Calls {@link #onTimeShiftPlay(Uri)}. 1682 */ timeShiftPlay(Uri recordedProgramUri)1683 void timeShiftPlay(Uri recordedProgramUri) { 1684 mCurrentPositionMs = 0; 1685 onTimeShiftPlay(recordedProgramUri); 1686 } 1687 1688 /** 1689 * Calls {@link #onTimeShiftPause}. 1690 */ timeShiftPause()1691 void timeShiftPause() { 1692 onTimeShiftPause(); 1693 } 1694 1695 /** 1696 * Calls {@link #onTimeShiftResume}. 1697 */ timeShiftResume()1698 void timeShiftResume() { 1699 onTimeShiftResume(); 1700 } 1701 1702 /** 1703 * Calls {@link #onTimeShiftSeekTo}. 1704 */ timeShiftSeekTo(long timeMs)1705 void timeShiftSeekTo(long timeMs) { 1706 onTimeShiftSeekTo(timeMs); 1707 } 1708 1709 /** 1710 * Calls {@link #onTimeShiftSetPlaybackParams}. 1711 */ timeShiftSetPlaybackParams(PlaybackParams params)1712 void timeShiftSetPlaybackParams(PlaybackParams params) { 1713 onTimeShiftSetPlaybackParams(params); 1714 } 1715 1716 /** 1717 * Enable/disable position tracking. 1718 * 1719 * @param enable {@code true} to enable tracking, {@code false} otherwise. 1720 */ timeShiftEnablePositionTracking(boolean enable)1721 void timeShiftEnablePositionTracking(boolean enable) { 1722 if (enable) { 1723 mHandler.post(mTimeShiftPositionTrackingRunnable); 1724 } else { 1725 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable); 1726 mStartPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 1727 mCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 1728 } 1729 } 1730 1731 /** 1732 * Schedules a task which checks whether the overlay view is detached and kills the process 1733 * if it is not. Note that this method is expected to be called in a non-main thread. 1734 */ scheduleOverlayViewCleanup()1735 void scheduleOverlayViewCleanup() { 1736 View overlayViewParent = mOverlayViewContainer; 1737 if (overlayViewParent != null) { 1738 mOverlayViewCleanUpTask = new OverlayViewCleanUpTask(); 1739 mOverlayViewCleanUpTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 1740 overlayViewParent); 1741 } 1742 } 1743 requestBroadcastInfo(BroadcastInfoRequest request)1744 void requestBroadcastInfo(BroadcastInfoRequest request) { 1745 onRequestBroadcastInfo(request); 1746 } 1747 removeBroadcastInfo(int requestId)1748 void removeBroadcastInfo(int requestId) { 1749 onRemoveBroadcastInfo(requestId); 1750 } 1751 requestAd(AdRequest request)1752 void requestAd(AdRequest request) { 1753 onRequestAd(request); 1754 } 1755 1756 /** 1757 * Takes care of dispatching incoming input events and tells whether the event was handled. 1758 */ dispatchInputEvent(InputEvent event, InputEventReceiver receiver)1759 int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) { 1760 if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")"); 1761 boolean isNavigationKey = false; 1762 boolean skipDispatchToOverlayView = false; 1763 if (event instanceof KeyEvent) { 1764 KeyEvent keyEvent = (KeyEvent) event; 1765 if (keyEvent.dispatch(this, mDispatcherState, this)) { 1766 return TvInputManager.Session.DISPATCH_HANDLED; 1767 } 1768 isNavigationKey = isNavigationKey(keyEvent.getKeyCode()); 1769 // When media keys and KEYCODE_MEDIA_AUDIO_TRACK are dispatched to ViewRootImpl, 1770 // ViewRootImpl always consumes the keys. In this case, the application loses 1771 // a chance to handle media keys. Therefore, media keys are not dispatched to 1772 // ViewRootImpl. 1773 skipDispatchToOverlayView = KeyEvent.isMediaSessionKey(keyEvent.getKeyCode()) 1774 || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK; 1775 } else if (event instanceof MotionEvent) { 1776 MotionEvent motionEvent = (MotionEvent) event; 1777 final int source = motionEvent.getSource(); 1778 if (motionEvent.isTouchEvent()) { 1779 if (onTouchEvent(motionEvent)) { 1780 return TvInputManager.Session.DISPATCH_HANDLED; 1781 } 1782 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 1783 if (onTrackballEvent(motionEvent)) { 1784 return TvInputManager.Session.DISPATCH_HANDLED; 1785 } 1786 } else { 1787 if (onGenericMotionEvent(motionEvent)) { 1788 return TvInputManager.Session.DISPATCH_HANDLED; 1789 } 1790 } 1791 } 1792 if (mOverlayViewContainer == null || !mOverlayViewContainer.isAttachedToWindow() 1793 || skipDispatchToOverlayView) { 1794 return TvInputManager.Session.DISPATCH_NOT_HANDLED; 1795 } 1796 if (!mOverlayViewContainer.hasWindowFocus()) { 1797 ViewRootImpl viewRoot = mOverlayViewContainer.getViewRootImpl(); 1798 viewRoot.windowFocusChanged(true); 1799 } 1800 if (isNavigationKey && mOverlayViewContainer.hasFocusable()) { 1801 // If mOverlayView has focusable views, navigation key events should be always 1802 // handled. If not, it can make the application UI navigation messed up. 1803 // For example, in the case that the left-most view is focused, a left key event 1804 // will not be handled in ViewRootImpl. Then, the left key event will be handled in 1805 // the application during the UI navigation of the TV input. 1806 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event); 1807 return TvInputManager.Session.DISPATCH_HANDLED; 1808 } else { 1809 mOverlayViewContainer.getViewRootImpl().dispatchInputEvent(event, receiver); 1810 return TvInputManager.Session.DISPATCH_IN_PROGRESS; 1811 } 1812 } 1813 initialize(ITvInputSessionCallback callback)1814 private void initialize(ITvInputSessionCallback callback) { 1815 synchronized(mLock) { 1816 mSessionCallback = callback; 1817 for (Runnable runnable : mPendingActions) { 1818 runnable.run(); 1819 } 1820 mPendingActions.clear(); 1821 } 1822 } 1823 executeOrPostRunnableOnMainThread(Runnable action)1824 private void executeOrPostRunnableOnMainThread(Runnable action) { 1825 synchronized(mLock) { 1826 if (mSessionCallback == null) { 1827 // The session is not initialized yet. 1828 mPendingActions.add(action); 1829 } else { 1830 if (mHandler.getLooper().isCurrentThread()) { 1831 action.run(); 1832 } else { 1833 // Posts the runnable if this is not called from the main thread 1834 mHandler.post(action); 1835 } 1836 } 1837 } 1838 } 1839 1840 private final class TimeShiftPositionTrackingRunnable implements Runnable { 1841 @Override run()1842 public void run() { 1843 long startPositionMs = onTimeShiftGetStartPosition(); 1844 if (mStartPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME 1845 || mStartPositionMs != startPositionMs) { 1846 mStartPositionMs = startPositionMs; 1847 notifyTimeShiftStartPositionChanged(startPositionMs); 1848 } 1849 long currentPositionMs = onTimeShiftGetCurrentPosition(); 1850 if (currentPositionMs < mStartPositionMs) { 1851 Log.w(TAG, "Current position (" + currentPositionMs + ") cannot be earlier than" 1852 + " start position (" + mStartPositionMs + "). Reset to the start " 1853 + "position."); 1854 currentPositionMs = mStartPositionMs; 1855 } 1856 if (mCurrentPositionMs == TvInputManager.TIME_SHIFT_INVALID_TIME 1857 || mCurrentPositionMs != currentPositionMs) { 1858 mCurrentPositionMs = currentPositionMs; 1859 notifyTimeShiftCurrentPositionChanged(currentPositionMs); 1860 } 1861 mHandler.removeCallbacks(mTimeShiftPositionTrackingRunnable); 1862 mHandler.postDelayed(mTimeShiftPositionTrackingRunnable, 1863 POSITION_UPDATE_INTERVAL_MS); 1864 } 1865 } 1866 } 1867 1868 private static final class OverlayViewCleanUpTask extends AsyncTask<View, Void, Void> { 1869 @Override doInBackground(View... views)1870 protected Void doInBackground(View... views) { 1871 View overlayViewParent = views[0]; 1872 try { 1873 Thread.sleep(DETACH_OVERLAY_VIEW_TIMEOUT_MS); 1874 } catch (InterruptedException e) { 1875 return null; 1876 } 1877 if (isCancelled()) { 1878 return null; 1879 } 1880 if (overlayViewParent.isAttachedToWindow()) { 1881 Log.e(TAG, "Time out on releasing overlay view. Killing " 1882 + overlayViewParent.getContext().getPackageName()); 1883 Process.killProcess(Process.myPid()); 1884 } 1885 return null; 1886 } 1887 } 1888 1889 /** 1890 * Base class for derived classes to implement to provide a TV input recording session. 1891 */ 1892 public abstract static class RecordingSession { 1893 final Handler mHandler; 1894 1895 private final Object mLock = new Object(); 1896 // @GuardedBy("mLock") 1897 private ITvInputSessionCallback mSessionCallback; 1898 // @GuardedBy("mLock") 1899 private final List<Runnable> mPendingActions = new ArrayList<>(); 1900 1901 /** 1902 * Creates a new RecordingSession. 1903 * 1904 * @param context The context of the application 1905 */ RecordingSession(Context context)1906 public RecordingSession(Context context) { 1907 mHandler = new Handler(context.getMainLooper()); 1908 } 1909 1910 /** 1911 * Informs the application that this recording session has been tuned to the given channel 1912 * and is ready to start recording. 1913 * 1914 * <p>Upon receiving a call to {@link #onTune(Uri)}, the session is expected to tune to the 1915 * passed channel and call this method to indicate that it is now available for immediate 1916 * recording. When {@link #onStartRecording(Uri)} is called, recording must start with 1917 * minimal delay. 1918 * 1919 * @param channelUri The URI of a channel. 1920 */ notifyTuned(Uri channelUri)1921 public void notifyTuned(Uri channelUri) { 1922 executeOrPostRunnableOnMainThread(new Runnable() { 1923 @MainThread 1924 @Override 1925 public void run() { 1926 try { 1927 if (DEBUG) Log.d(TAG, "notifyTuned"); 1928 if (mSessionCallback != null) { 1929 mSessionCallback.onTuned(channelUri); 1930 } 1931 } catch (RemoteException e) { 1932 Log.w(TAG, "error in notifyTuned", e); 1933 } 1934 } 1935 }); 1936 } 1937 1938 /** 1939 * Informs the application that this recording session has stopped recording and created a 1940 * new data entry in the {@link TvContract.RecordedPrograms} table that describes the newly 1941 * recorded program. 1942 * 1943 * <p>The recording session must call this method in response to {@link #onStopRecording()}. 1944 * The session may call it even before receiving a call to {@link #onStopRecording()} if a 1945 * partially recorded program is available when there is an error. 1946 * 1947 * @param recordedProgramUri The URI of the newly recorded program. 1948 */ notifyRecordingStopped(final Uri recordedProgramUri)1949 public void notifyRecordingStopped(final Uri recordedProgramUri) { 1950 executeOrPostRunnableOnMainThread(new Runnable() { 1951 @MainThread 1952 @Override 1953 public void run() { 1954 try { 1955 if (DEBUG) Log.d(TAG, "notifyRecordingStopped"); 1956 if (mSessionCallback != null) { 1957 mSessionCallback.onRecordingStopped(recordedProgramUri); 1958 } 1959 } catch (RemoteException e) { 1960 Log.w(TAG, "error in notifyRecordingStopped", e); 1961 } 1962 } 1963 }); 1964 } 1965 1966 /** 1967 * Informs the application that there is an error and this recording session is no longer 1968 * able to start or continue recording. It may be called at any time after the recording 1969 * session is created until {@link #onRelease()} is called. 1970 * 1971 * <p>The application may release the current session upon receiving the error code through 1972 * {@link TvRecordingClient.RecordingCallback#onError(int)}. The session may call 1973 * {@link #notifyRecordingStopped(Uri)} if a partially recorded but still playable program 1974 * is available, before calling this method. 1975 * 1976 * @param error The error code. Should be one of the followings. 1977 * <ul> 1978 * <li>{@link TvInputManager#RECORDING_ERROR_UNKNOWN} 1979 * <li>{@link TvInputManager#RECORDING_ERROR_INSUFFICIENT_SPACE} 1980 * <li>{@link TvInputManager#RECORDING_ERROR_RESOURCE_BUSY} 1981 * </ul> 1982 */ notifyError(@vInputManager.RecordingError int error)1983 public void notifyError(@TvInputManager.RecordingError int error) { 1984 if (error < TvInputManager.RECORDING_ERROR_START 1985 || error > TvInputManager.RECORDING_ERROR_END) { 1986 Log.w(TAG, "notifyError - invalid error code (" + error 1987 + ") is changed to RECORDING_ERROR_UNKNOWN."); 1988 error = TvInputManager.RECORDING_ERROR_UNKNOWN; 1989 } 1990 final int validError = error; 1991 executeOrPostRunnableOnMainThread(new Runnable() { 1992 @MainThread 1993 @Override 1994 public void run() { 1995 try { 1996 if (DEBUG) Log.d(TAG, "notifyError"); 1997 if (mSessionCallback != null) { 1998 mSessionCallback.onError(validError); 1999 } 2000 } catch (RemoteException e) { 2001 Log.w(TAG, "error in notifyError", e); 2002 } 2003 } 2004 }); 2005 } 2006 2007 /** 2008 * Dispatches an event to the application using this recording session. 2009 * 2010 * @param eventType The type of the event. 2011 * @param eventArgs Optional arguments of the event. 2012 * @hide 2013 */ 2014 @SystemApi notifySessionEvent(@onNull final String eventType, final Bundle eventArgs)2015 public void notifySessionEvent(@NonNull final String eventType, final Bundle eventArgs) { 2016 Preconditions.checkNotNull(eventType); 2017 executeOrPostRunnableOnMainThread(new Runnable() { 2018 @MainThread 2019 @Override 2020 public void run() { 2021 try { 2022 if (DEBUG) Log.d(TAG, "notifySessionEvent(" + eventType + ")"); 2023 if (mSessionCallback != null) { 2024 mSessionCallback.onSessionEvent(eventType, eventArgs); 2025 } 2026 } catch (RemoteException e) { 2027 Log.w(TAG, "error in sending event (event=" + eventType + ")", e); 2028 } 2029 } 2030 }); 2031 } 2032 2033 /** 2034 * Called when the application requests to tune to a given channel for TV program recording. 2035 * 2036 * <p>The application may call this method before starting or after stopping recording, but 2037 * not during recording. 2038 * 2039 * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or 2040 * {@link #notifyError(int)} otherwise. 2041 * 2042 * @param channelUri The URI of a channel. 2043 */ onTune(Uri channelUri)2044 public abstract void onTune(Uri channelUri); 2045 2046 /** 2047 * Called when the application requests to tune to a given channel for TV program recording. 2048 * Override this method in order to handle domain-specific features that are only known 2049 * between certain TV inputs and their clients. 2050 * 2051 * <p>The application may call this method before starting or after stopping recording, but 2052 * not during recording. The default implementation calls {@link #onTune(Uri)}. 2053 * 2054 * <p>The session must call {@link #notifyTuned(Uri)} if the tune request was fulfilled, or 2055 * {@link #notifyError(int)} otherwise. 2056 * 2057 * @param channelUri The URI of a channel. 2058 * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped 2059 * name, i.e. prefixed with a package name you own, so that different developers 2060 * will not create conflicting keys. 2061 */ onTune(Uri channelUri, Bundle params)2062 public void onTune(Uri channelUri, Bundle params) { 2063 onTune(channelUri); 2064 } 2065 2066 /** 2067 * Called when the application requests to start TV program recording. Recording must start 2068 * immediately when this method is called. 2069 * 2070 * <p>The application may supply the URI for a TV program for filling in program specific 2071 * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table. 2072 * A non-null {@code programUri} implies the started recording should be of that specific 2073 * program, whereas null {@code programUri} does not impose such a requirement and the 2074 * recording can span across multiple TV programs. In either case, the application must call 2075 * {@link TvRecordingClient#stopRecording()} to stop the recording. 2076 * 2077 * <p>The session must call {@link #notifyError(int)} if the start request cannot be 2078 * fulfilled. 2079 * 2080 * @param programUri The URI for the TV program to record, built by 2081 * {@link TvContract#buildProgramUri(long)}. Can be {@code null}. 2082 */ onStartRecording(@ullable Uri programUri)2083 public abstract void onStartRecording(@Nullable Uri programUri); 2084 2085 /** 2086 * Called when the application requests to start TV program recording. Recording must start 2087 * immediately when this method is called. 2088 * 2089 * <p>The application may supply the URI for a TV program for filling in program specific 2090 * data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table. 2091 * A non-null {@code programUri} implies the started recording should be of that specific 2092 * program, whereas null {@code programUri} does not impose such a requirement and the 2093 * recording can span across multiple TV programs. In either case, the application must call 2094 * {@link TvRecordingClient#stopRecording()} to stop the recording. 2095 * 2096 * <p>The session must call {@link #notifyError(int)} if the start request cannot be 2097 * fulfilled. 2098 * 2099 * @param programUri The URI for the TV program to record, built by 2100 * {@link TvContract#buildProgramUri(long)}. Can be {@code null}. 2101 * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped 2102 * name, i.e. prefixed with a package name you own, so that different developers 2103 * will not create conflicting keys. 2104 */ onStartRecording(@ullable Uri programUri, @NonNull Bundle params)2105 public void onStartRecording(@Nullable Uri programUri, @NonNull Bundle params) { 2106 onStartRecording(programUri); 2107 } 2108 2109 /** 2110 * Called when the application requests to stop TV program recording. Recording must stop 2111 * immediately when this method is called. 2112 * 2113 * <p>The session must create a new data entry in the 2114 * {@link android.media.tv.TvContract.RecordedPrograms} table that describes the newly 2115 * recorded program and call {@link #notifyRecordingStopped(Uri)} with the URI to that 2116 * entry. 2117 * If the stop request cannot be fulfilled, the session must call {@link #notifyError(int)}. 2118 * 2119 */ onStopRecording()2120 public abstract void onStopRecording(); 2121 2122 2123 /** 2124 * Called when the application requests to pause TV program recording. Recording must pause 2125 * immediately when this method is called. 2126 * 2127 * If the pause request cannot be fulfilled, the session must call 2128 * {@link #notifyError(int)}. 2129 * 2130 * @param params Domain-specific data for recording request. 2131 */ onPauseRecording(@onNull Bundle params)2132 public void onPauseRecording(@NonNull Bundle params) { } 2133 2134 /** 2135 * Called when the application requests to resume TV program recording. Recording must 2136 * resume immediately when this method is called. 2137 * 2138 * If the resume request cannot be fulfilled, the session must call 2139 * {@link #notifyError(int)}. 2140 * 2141 * @param params Domain-specific data for recording request. 2142 */ onResumeRecording(@onNull Bundle params)2143 public void onResumeRecording(@NonNull Bundle params) { } 2144 2145 /** 2146 * Called when the application requests to release all the resources held by this recording 2147 * session. 2148 */ onRelease()2149 public abstract void onRelease(); 2150 2151 /** 2152 * Processes a private command sent from the application to the TV input. This can be used 2153 * to provide domain-specific features that are only known between certain TV inputs and 2154 * their clients. 2155 * 2156 * @param action Name of the command to be performed. This <em>must</em> be a scoped name, 2157 * i.e. prefixed with a package name you own, so that different developers will 2158 * not create conflicting commands. 2159 * @param data Any data to include with the command. 2160 */ onAppPrivateCommand(@onNull String action, Bundle data)2161 public void onAppPrivateCommand(@NonNull String action, Bundle data) { 2162 } 2163 2164 /** 2165 * Calls {@link #onTune(Uri, Bundle)}. 2166 * 2167 */ tune(Uri channelUri, Bundle params)2168 void tune(Uri channelUri, Bundle params) { 2169 onTune(channelUri, params); 2170 } 2171 2172 /** 2173 * Calls {@link #onRelease()}. 2174 * 2175 */ release()2176 void release() { 2177 onRelease(); 2178 } 2179 2180 /** 2181 * Calls {@link #onStartRecording(Uri, Bundle)}. 2182 * 2183 */ startRecording(@ullable Uri programUri, @NonNull Bundle params)2184 void startRecording(@Nullable Uri programUri, @NonNull Bundle params) { 2185 onStartRecording(programUri, params); 2186 } 2187 2188 /** 2189 * Calls {@link #onStopRecording()}. 2190 * 2191 */ stopRecording()2192 void stopRecording() { 2193 onStopRecording(); 2194 } 2195 2196 /** 2197 * Calls {@link #onPauseRecording(Bundle)}. 2198 * 2199 */ pauseRecording(@onNull Bundle params)2200 void pauseRecording(@NonNull Bundle params) { 2201 onPauseRecording(params); 2202 } 2203 2204 /** 2205 * Calls {@link #onResumeRecording(Bundle)}. 2206 * 2207 */ resumeRecording(@onNull Bundle params)2208 void resumeRecording(@NonNull Bundle params) { 2209 onResumeRecording(params); 2210 } 2211 2212 /** 2213 * Calls {@link #onAppPrivateCommand(String, Bundle)}. 2214 */ appPrivateCommand(String action, Bundle data)2215 void appPrivateCommand(String action, Bundle data) { 2216 onAppPrivateCommand(action, data); 2217 } 2218 initialize(ITvInputSessionCallback callback)2219 private void initialize(ITvInputSessionCallback callback) { 2220 synchronized(mLock) { 2221 mSessionCallback = callback; 2222 for (Runnable runnable : mPendingActions) { 2223 runnable.run(); 2224 } 2225 mPendingActions.clear(); 2226 } 2227 } 2228 executeOrPostRunnableOnMainThread(Runnable action)2229 private void executeOrPostRunnableOnMainThread(Runnable action) { 2230 synchronized(mLock) { 2231 if (mSessionCallback == null) { 2232 // The session is not initialized yet. 2233 mPendingActions.add(action); 2234 } else { 2235 if (mHandler.getLooper().isCurrentThread()) { 2236 action.run(); 2237 } else { 2238 // Posts the runnable if this is not called from the main thread 2239 mHandler.post(action); 2240 } 2241 } 2242 } 2243 } 2244 } 2245 2246 /** 2247 * Base class for a TV input session which represents an external device connected to a 2248 * hardware TV input. 2249 * 2250 * <p>This class is for an input which provides channels for the external set-top box to the 2251 * application. Once a TV input returns an implementation of this class on 2252 * {@link #onCreateSession(String)}, the framework will create a separate session for 2253 * a hardware TV Input (e.g. HDMI 1) and forward the application's surface to the session so 2254 * that the user can see the screen of the hardware TV Input when she tunes to a channel from 2255 * this TV input. The implementation of this class is expected to change the channel of the 2256 * external set-top box via a proprietary protocol when {@link HardwareSession#onTune} is 2257 * requested by the application. 2258 * 2259 * <p>Note that this class is not for inputs for internal hardware like built-in tuner and HDMI 2260 * 1. 2261 * 2262 * @see #onCreateSession(String) 2263 */ 2264 public abstract static class HardwareSession extends Session { 2265 2266 /** 2267 * Creates a new HardwareSession. 2268 * 2269 * @param context The context of the application 2270 */ HardwareSession(Context context)2271 public HardwareSession(Context context) { 2272 super(context); 2273 } 2274 2275 private TvInputManager.Session mHardwareSession; 2276 private ITvInputSession mProxySession; 2277 private ITvInputSessionCallback mProxySessionCallback; 2278 private Handler mServiceHandler; 2279 2280 /** 2281 * Returns the hardware TV input ID the external device is connected to. 2282 * 2283 * <p>TV input is expected to provide {@link android.R.attr#setupActivity} so that 2284 * the application can launch it before using this TV input. The setup activity may let 2285 * the user select the hardware TV input to which the external device is connected. The ID 2286 * of the selected one should be stored in the TV input so that it can be returned here. 2287 */ getHardwareInputId()2288 public abstract String getHardwareInputId(); 2289 2290 private final TvInputManager.SessionCallback mHardwareSessionCallback = 2291 new TvInputManager.SessionCallback() { 2292 @Override 2293 public void onSessionCreated(TvInputManager.Session session) { 2294 mHardwareSession = session; 2295 SomeArgs args = SomeArgs.obtain(); 2296 if (session != null) { 2297 args.arg1 = HardwareSession.this; 2298 args.arg2 = mProxySession; 2299 args.arg3 = mProxySessionCallback; 2300 args.arg4 = session.getToken(); 2301 session.tune(TvContract.buildChannelUriForPassthroughInput( 2302 getHardwareInputId())); 2303 } else { 2304 args.arg1 = null; 2305 args.arg2 = null; 2306 args.arg3 = mProxySessionCallback; 2307 args.arg4 = null; 2308 onRelease(); 2309 } 2310 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, 2311 args).sendToTarget(); 2312 } 2313 2314 @Override 2315 public void onVideoAvailable(final TvInputManager.Session session) { 2316 if (mHardwareSession == session) { 2317 onHardwareVideoAvailable(); 2318 } 2319 } 2320 2321 @Override 2322 public void onVideoUnavailable(final TvInputManager.Session session, 2323 final int reason) { 2324 if (mHardwareSession == session) { 2325 onHardwareVideoUnavailable(reason); 2326 } 2327 } 2328 }; 2329 2330 /** 2331 * This method will not be called in {@link HardwareSession}. Framework will 2332 * forward the application's surface to the hardware TV input. 2333 */ 2334 @Override onSetSurface(Surface surface)2335 public final boolean onSetSurface(Surface surface) { 2336 Log.e(TAG, "onSetSurface() should not be called in HardwareProxySession."); 2337 return false; 2338 } 2339 2340 /** 2341 * Called when the underlying hardware TV input session calls 2342 * {@link TvInputService.Session#notifyVideoAvailable()}. 2343 */ onHardwareVideoAvailable()2344 public void onHardwareVideoAvailable() { } 2345 2346 /** 2347 * Called when the underlying hardware TV input session calls 2348 * {@link TvInputService.Session#notifyVideoUnavailable(int)}. 2349 * 2350 * @param reason The reason that the hardware TV input stopped the playback: 2351 * <ul> 2352 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 2353 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} 2354 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 2355 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 2356 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY} 2357 * </ul> 2358 */ onHardwareVideoUnavailable(int reason)2359 public void onHardwareVideoUnavailable(int reason) { } 2360 2361 @Override release()2362 void release() { 2363 if (mHardwareSession != null) { 2364 mHardwareSession.release(); 2365 mHardwareSession = null; 2366 } 2367 super.release(); 2368 } 2369 } 2370 2371 /** @hide */ isNavigationKey(int keyCode)2372 public static boolean isNavigationKey(int keyCode) { 2373 switch (keyCode) { 2374 case KeyEvent.KEYCODE_DPAD_LEFT: 2375 case KeyEvent.KEYCODE_DPAD_RIGHT: 2376 case KeyEvent.KEYCODE_DPAD_UP: 2377 case KeyEvent.KEYCODE_DPAD_DOWN: 2378 case KeyEvent.KEYCODE_DPAD_CENTER: 2379 case KeyEvent.KEYCODE_PAGE_UP: 2380 case KeyEvent.KEYCODE_PAGE_DOWN: 2381 case KeyEvent.KEYCODE_MOVE_HOME: 2382 case KeyEvent.KEYCODE_MOVE_END: 2383 case KeyEvent.KEYCODE_TAB: 2384 case KeyEvent.KEYCODE_SPACE: 2385 case KeyEvent.KEYCODE_ENTER: 2386 return true; 2387 } 2388 return false; 2389 } 2390 2391 @SuppressLint("HandlerLeak") 2392 private final class ServiceHandler extends Handler { 2393 private static final int DO_CREATE_SESSION = 1; 2394 private static final int DO_NOTIFY_SESSION_CREATED = 2; 2395 private static final int DO_CREATE_RECORDING_SESSION = 3; 2396 private static final int DO_ADD_HARDWARE_INPUT = 4; 2397 private static final int DO_REMOVE_HARDWARE_INPUT = 5; 2398 private static final int DO_ADD_HDMI_INPUT = 6; 2399 private static final int DO_REMOVE_HDMI_INPUT = 7; 2400 private static final int DO_UPDATE_HDMI_INPUT = 8; 2401 broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo)2402 private void broadcastAddHardwareInput(int deviceId, TvInputInfo inputInfo) { 2403 int n = mCallbacks.beginBroadcast(); 2404 for (int i = 0; i < n; ++i) { 2405 try { 2406 mCallbacks.getBroadcastItem(i).addHardwareInput(deviceId, inputInfo); 2407 } catch (RemoteException e) { 2408 Log.e(TAG, "error in broadcastAddHardwareInput", e); 2409 } 2410 } 2411 mCallbacks.finishBroadcast(); 2412 } 2413 broadcastAddHdmiInput(int id, TvInputInfo inputInfo)2414 private void broadcastAddHdmiInput(int id, TvInputInfo inputInfo) { 2415 int n = mCallbacks.beginBroadcast(); 2416 for (int i = 0; i < n; ++i) { 2417 try { 2418 mCallbacks.getBroadcastItem(i).addHdmiInput(id, inputInfo); 2419 } catch (RemoteException e) { 2420 Log.e(TAG, "error in broadcastAddHdmiInput", e); 2421 } 2422 } 2423 mCallbacks.finishBroadcast(); 2424 } 2425 broadcastRemoveHardwareInput(String inputId)2426 private void broadcastRemoveHardwareInput(String inputId) { 2427 int n = mCallbacks.beginBroadcast(); 2428 for (int i = 0; i < n; ++i) { 2429 try { 2430 mCallbacks.getBroadcastItem(i).removeHardwareInput(inputId); 2431 } catch (RemoteException e) { 2432 Log.e(TAG, "error in broadcastRemoveHardwareInput", e); 2433 } 2434 } 2435 mCallbacks.finishBroadcast(); 2436 } 2437 2438 @Override handleMessage(Message msg)2439 public final void handleMessage(Message msg) { 2440 switch (msg.what) { 2441 case DO_CREATE_SESSION: { 2442 SomeArgs args = (SomeArgs) msg.obj; 2443 InputChannel channel = (InputChannel) args.arg1; 2444 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2; 2445 String inputId = (String) args.arg3; 2446 String sessionId = (String) args.arg4; 2447 args.recycle(); 2448 Session sessionImpl = onCreateSession(inputId, sessionId); 2449 if (sessionImpl == null) { 2450 try { 2451 // Failed to create a session. 2452 cb.onSessionCreated(null, null); 2453 } catch (RemoteException e) { 2454 Log.e(TAG, "error in onSessionCreated", e); 2455 } 2456 return; 2457 } 2458 ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, 2459 sessionImpl, channel); 2460 if (sessionImpl instanceof HardwareSession) { 2461 HardwareSession proxySession = 2462 ((HardwareSession) sessionImpl); 2463 String hardwareInputId = proxySession.getHardwareInputId(); 2464 if (TextUtils.isEmpty(hardwareInputId) || 2465 !isPassthroughInput(hardwareInputId)) { 2466 if (TextUtils.isEmpty(hardwareInputId)) { 2467 Log.w(TAG, "Hardware input id is not setup yet."); 2468 } else { 2469 Log.w(TAG, "Invalid hardware input id : " + hardwareInputId); 2470 } 2471 sessionImpl.onRelease(); 2472 try { 2473 cb.onSessionCreated(null, null); 2474 } catch (RemoteException e) { 2475 Log.e(TAG, "error in onSessionCreated", e); 2476 } 2477 return; 2478 } 2479 proxySession.mProxySession = stub; 2480 proxySession.mProxySessionCallback = cb; 2481 proxySession.mServiceHandler = mServiceHandler; 2482 TvInputManager manager = (TvInputManager) getSystemService( 2483 Context.TV_INPUT_SERVICE); 2484 manager.createSession(hardwareInputId, 2485 proxySession.mHardwareSessionCallback, mServiceHandler); 2486 } else { 2487 SomeArgs someArgs = SomeArgs.obtain(); 2488 someArgs.arg1 = sessionImpl; 2489 someArgs.arg2 = stub; 2490 someArgs.arg3 = cb; 2491 someArgs.arg4 = null; 2492 mServiceHandler.obtainMessage(ServiceHandler.DO_NOTIFY_SESSION_CREATED, 2493 someArgs).sendToTarget(); 2494 } 2495 return; 2496 } 2497 case DO_NOTIFY_SESSION_CREATED: { 2498 SomeArgs args = (SomeArgs) msg.obj; 2499 Session sessionImpl = (Session) args.arg1; 2500 ITvInputSession stub = (ITvInputSession) args.arg2; 2501 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg3; 2502 IBinder hardwareSessionToken = (IBinder) args.arg4; 2503 try { 2504 cb.onSessionCreated(stub, hardwareSessionToken); 2505 } catch (RemoteException e) { 2506 Log.e(TAG, "error in onSessionCreated", e); 2507 } 2508 if (sessionImpl != null) { 2509 sessionImpl.initialize(cb); 2510 } 2511 args.recycle(); 2512 return; 2513 } 2514 case DO_CREATE_RECORDING_SESSION: { 2515 SomeArgs args = (SomeArgs) msg.obj; 2516 ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1; 2517 String inputId = (String) args.arg2; 2518 String sessionId = (String) args.arg3; 2519 args.recycle(); 2520 RecordingSession recordingSessionImpl = 2521 onCreateRecordingSession(inputId, sessionId); 2522 if (recordingSessionImpl == null) { 2523 try { 2524 // Failed to create a recording session. 2525 cb.onSessionCreated(null, null); 2526 } catch (RemoteException e) { 2527 Log.e(TAG, "error in onSessionCreated", e); 2528 } 2529 return; 2530 } 2531 ITvInputSession stub = new ITvInputSessionWrapper(TvInputService.this, 2532 recordingSessionImpl); 2533 try { 2534 cb.onSessionCreated(stub, null); 2535 } catch (RemoteException e) { 2536 Log.e(TAG, "error in onSessionCreated", e); 2537 } 2538 recordingSessionImpl.initialize(cb); 2539 return; 2540 } 2541 case DO_ADD_HARDWARE_INPUT: { 2542 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj; 2543 TvInputInfo inputInfo = onHardwareAdded(hardwareInfo); 2544 if (inputInfo != null) { 2545 broadcastAddHardwareInput(hardwareInfo.getDeviceId(), inputInfo); 2546 } 2547 return; 2548 } 2549 case DO_REMOVE_HARDWARE_INPUT: { 2550 TvInputHardwareInfo hardwareInfo = (TvInputHardwareInfo) msg.obj; 2551 String inputId = onHardwareRemoved(hardwareInfo); 2552 if (inputId != null) { 2553 broadcastRemoveHardwareInput(inputId); 2554 } 2555 return; 2556 } 2557 case DO_ADD_HDMI_INPUT: { 2558 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 2559 TvInputInfo inputInfo = onHdmiDeviceAdded(deviceInfo); 2560 if (inputInfo != null) { 2561 broadcastAddHdmiInput(deviceInfo.getId(), inputInfo); 2562 } 2563 return; 2564 } 2565 case DO_REMOVE_HDMI_INPUT: { 2566 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 2567 String inputId = onHdmiDeviceRemoved(deviceInfo); 2568 if (inputId != null) { 2569 broadcastRemoveHardwareInput(inputId); 2570 } 2571 return; 2572 } 2573 case DO_UPDATE_HDMI_INPUT: { 2574 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 2575 onHdmiDeviceUpdated(deviceInfo); 2576 return; 2577 } 2578 default: { 2579 Log.w(TAG, "Unhandled message code: " + msg.what); 2580 return; 2581 } 2582 } 2583 } 2584 } 2585 } 2586