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