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