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