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