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