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