TvView.java revision d0f00588834806d3f52c95c2d5fb13d9a92bddfc
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.SystemApi; 20import android.content.Context; 21import android.graphics.Rect; 22import android.media.tv.TvInputManager.Session; 23import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; 24import android.media.tv.TvInputManager.SessionCallback; 25import android.net.Uri; 26import android.os.Bundle; 27import android.os.Handler; 28import android.text.TextUtils; 29import android.util.AttributeSet; 30import android.util.Log; 31import android.view.InputEvent; 32import android.view.KeyEvent; 33import android.view.MotionEvent; 34import android.view.Surface; 35import android.view.SurfaceHolder; 36import android.view.SurfaceView; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.ViewRootImpl; 40 41import java.lang.ref.WeakReference; 42import java.util.List; 43 44/** 45 * Displays TV contents. The TvView class provides a high level interface for applications to show 46 * TV programs from various TV sources that implement {@link TvInputService}. (Note that the list of 47 * TV inputs available on the system can be obtained by calling 48 * {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.) 49 * <p> 50 * Once the application supplies the URI for a specific TV channel to {@link #tune(String, Uri)} 51 * method, it takes care of underlying service binding (and unbinding if the current TvView is 52 * already bound to a service) and automatically allocates/deallocates resources needed. In addition 53 * to a few essential methods to control how the contents are presented, it also provides a way to 54 * dispatch input events to the connected TvInputService in order to enable custom key actions for 55 * the TV input. 56 * </p> 57 */ 58public class TvView extends ViewGroup { 59 private static final String TAG = "TvView"; 60 // STOPSHIP: Turn debugging off. 61 private static final boolean DEBUG = true; 62 63 private static final int VIDEO_SIZE_VALUE_UNKNOWN = 0; 64 65 private static final int ZORDER_MEDIA = 0; 66 private static final int ZORDER_MEDIA_OVERLAY = 1; 67 private static final int ZORDER_ON_TOP = 2; 68 69 private static final int CAPTION_DEFAULT = 0; 70 private static final int CAPTION_ENABLED = 1; 71 private static final int CAPTION_DISABLED = 2; 72 73 private static final WeakReference<TvView> NULL_TV_VIEW = new WeakReference(null); 74 75 private static final Object sMainTvViewLock = new Object(); 76 private static WeakReference<TvView> sMainTvView = NULL_TV_VIEW; 77 78 private final Handler mHandler = new Handler(); 79 private Session mSession; 80 private SurfaceView mSurfaceView; 81 private Surface mSurface; 82 private boolean mOverlayViewCreated; 83 private Rect mOverlayViewFrame; 84 private final TvInputManager mTvInputManager; 85 private MySessionCallback mSessionCallback; 86 private TvInputListener mListener; 87 private OnUnhandledInputEventListener mOnUnhandledInputEventListener; 88 private boolean mHasStreamVolume; 89 private float mStreamVolume; 90 private int mVideoWidth = VIDEO_SIZE_VALUE_UNKNOWN; 91 private int mVideoHeight = VIDEO_SIZE_VALUE_UNKNOWN; 92 private boolean mSurfaceChanged; 93 private int mSurfaceFormat; 94 private int mSurfaceWidth; 95 private int mSurfaceHeight; 96 private final AttributeSet mAttrs; 97 private final int mDefStyleAttr; 98 private int mWindowZOrder; 99 private boolean mUseRequestedSurfaceLayout; 100 private int mSurfaceViewLeft; 101 private int mSurfaceViewRight; 102 private int mSurfaceViewTop; 103 private int mSurfaceViewBottom; 104 private int mCaptionEnabled; 105 106 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 107 @Override 108 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 109 if (DEBUG) { 110 Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" 111 + width + ", height=" + height + ")"); 112 } 113 mSurfaceFormat = format; 114 mSurfaceWidth = width; 115 mSurfaceHeight = height; 116 mSurfaceChanged = true; 117 dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); 118 } 119 120 @Override 121 public void surfaceCreated(SurfaceHolder holder) { 122 mSurface = holder.getSurface(); 123 setSessionSurface(mSurface); 124 } 125 126 @Override 127 public void surfaceDestroyed(SurfaceHolder holder) { 128 mSurface = null; 129 mSurfaceChanged = false; 130 setSessionSurface(null); 131 } 132 }; 133 134 private final FinishedInputEventCallback mFinishedInputEventCallback = 135 new FinishedInputEventCallback() { 136 @Override 137 public void onFinishedInputEvent(Object token, boolean handled) { 138 if (DEBUG) { 139 Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")"); 140 } 141 if (handled) { 142 return; 143 } 144 // TODO: Re-order unhandled events. 145 InputEvent event = (InputEvent) token; 146 if (dispatchUnhandledInputEvent(event)) { 147 return; 148 } 149 ViewRootImpl viewRootImpl = getViewRootImpl(); 150 if (viewRootImpl != null) { 151 viewRootImpl.dispatchUnhandledInputEvent(event); 152 } 153 } 154 }; 155 156 public TvView(Context context) { 157 this(context, null, 0); 158 } 159 160 public TvView(Context context, AttributeSet attrs) { 161 this(context, attrs, 0); 162 } 163 164 public TvView(Context context, AttributeSet attrs, int defStyleAttr) { 165 super(context, attrs, defStyleAttr); 166 mAttrs = attrs; 167 mDefStyleAttr = defStyleAttr; 168 resetSurfaceView(); 169 mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); 170 } 171 172 /** 173 * Sets a listener for events in this TvView. 174 * 175 * @param listener The listener to be called with events. A value of {@code null} removes any 176 * existing listener. 177 */ 178 public void setTvInputListener(TvInputListener listener) { 179 mListener = listener; 180 } 181 182 /** 183 * Sets this as the main {@link TvView}. 184 * <p> 185 * The main {@link TvView} is a {@link TvView} whose corresponding TV input determines the 186 * HDMI-CEC active source device. For an HDMI port input, one of source devices that is 187 * connected to that HDMI port becomes the active source. For an HDMI-CEC logical device input, 188 * the corresponding HDMI-CEC logical device becomes the active source. For any non-HDMI input 189 * (including the tuner, composite, S-Video, etc.), the internal device (= TV itself) becomes 190 * the active source. 191 * </p><p> 192 * First tuned {@link TvView} becomes main automatically, and keeps to be main until either 193 * {@link #reset} is called for the main {@link TvView} or {@link #setMain} is called for other 194 * {@link TvView}. 195 * </p> 196 * @hide 197 */ 198 @SystemApi 199 public void setMain() { 200 synchronized (sMainTvViewLock) { 201 sMainTvView = new WeakReference(this); 202 if (hasWindowFocus() && mSession != null) { 203 mSession.setMain(); 204 } 205 } 206 } 207 208 /** 209 * Sets the Z order of a window owning the surface of this TvView above the normal TvView 210 * but below an application. 211 * 212 * @see SurfaceView#setZOrderMediaOverlay 213 * @hide 214 */ 215 @SystemApi 216 public void setZOrderMediaOverlay(boolean isMediaOverlay) { 217 if (isMediaOverlay) { 218 mWindowZOrder = ZORDER_MEDIA_OVERLAY; 219 removeSessionOverlayView(); 220 } else { 221 mWindowZOrder = ZORDER_MEDIA; 222 createSessionOverlayView(); 223 } 224 if (mSurfaceView != null) { 225 // ZOrderOnTop(false) removes WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 226 // from WindowLayoutParam as well as changes window type. 227 mSurfaceView.setZOrderOnTop(false); 228 mSurfaceView.setZOrderMediaOverlay(isMediaOverlay); 229 } 230 } 231 232 /** 233 * Sets the Z order of a window owning the surface of this TvView on top of an application. 234 * 235 * @see SurfaceView#setZOrderOnTop 236 * @hide 237 */ 238 @SystemApi 239 public void setZOrderOnTop(boolean onTop) { 240 if (onTop) { 241 mWindowZOrder = ZORDER_ON_TOP; 242 removeSessionOverlayView(); 243 } else { 244 mWindowZOrder = ZORDER_MEDIA; 245 createSessionOverlayView(); 246 } 247 if (mSurfaceView != null) { 248 mSurfaceView.setZOrderMediaOverlay(false); 249 mSurfaceView.setZOrderOnTop(onTop); 250 } 251 } 252 253 /** 254 * Sets the relative stream volume of this session to handle a change of audio focus. 255 * 256 * @param volume A volume value between 0.0f to 1.0f. 257 */ 258 public void setStreamVolume(float volume) { 259 if (DEBUG) Log.d(TAG, "setStreamVolume(" + volume + ")"); 260 mHasStreamVolume = true; 261 mStreamVolume = volume; 262 if (mSession == null) { 263 // Volume will be set once the connection has been made. 264 return; 265 } 266 mSession.setStreamVolume(volume); 267 } 268 269 /** 270 * Tunes to a given channel. 271 * 272 * @param inputId The ID of TV input which will play the given channel. 273 * @param channelUri The URI of a channel. 274 */ 275 public void tune(String inputId, Uri channelUri) { 276 tune(inputId, channelUri, null); 277 } 278 279 /** 280 * Tunes to a given channel. 281 * 282 * @param inputId The ID of TV input which will play the given channel. 283 * @param channelUri The URI of a channel. 284 * @param params Extra parameters which might be handled with the tune event. 285 * @hide 286 */ 287 @SystemApi 288 public void tune(String inputId, Uri channelUri, Bundle params) { 289 if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")"); 290 if (TextUtils.isEmpty(inputId)) { 291 throw new IllegalArgumentException("inputId cannot be null or an empty string"); 292 } 293 synchronized (sMainTvViewLock) { 294 if (sMainTvView.get() == null) { 295 sMainTvView = new WeakReference(this); 296 } 297 } 298 if (mSessionCallback != null && mSessionCallback.mInputId.equals(inputId)) { 299 if (mSession != null) { 300 mSession.tune(channelUri, params); 301 } else { 302 // Session is not created yet. Replace the channel which will be set once the 303 // session is made. 304 mSessionCallback.mChannelUri = channelUri; 305 mSessionCallback.mTuneParams = params; 306 } 307 } else { 308 resetInternal(); 309 // When createSession() is called multiple times before the callback is called, 310 // only the callback of the last createSession() call will be actually called back. 311 // The previous callbacks will be ignored. For the logic, mSessionCallback 312 // is newly assigned for every createSession request and compared with 313 // MySessionCreateCallback.this. 314 mSessionCallback = new MySessionCallback(inputId, channelUri, params); 315 mTvInputManager.createSession(inputId, mSessionCallback, mHandler); 316 } 317 } 318 319 /** 320 * Resets this TvView. 321 * <p> 322 * This method is primarily used to un-tune the current TvView. 323 */ 324 public void reset() { 325 if (DEBUG) Log.d(TAG, "reset()"); 326 synchronized (sMainTvViewLock) { 327 if (this == sMainTvView.get()) { 328 sMainTvView = NULL_TV_VIEW; 329 } 330 } 331 resetInternal(); 332 } 333 334 private void resetInternal() { 335 if (mSession != null) { 336 release(); 337 resetSurfaceView(); 338 } 339 } 340 341 /** 342 * Requests to unblock TV content according to the given rating. 343 * <p> 344 * This notifies TV input that blocked content is now OK to play. 345 * </p> 346 * 347 * @param unblockedRating A TvContentRating to unblock. 348 * @see TvInputService.Session#notifyContentBlocked(TvContentRating) 349 * @hide 350 */ 351 @SystemApi 352 public void requestUnblockContent(TvContentRating unblockedRating) { 353 if (mSession != null) { 354 mSession.requestUnblockContent(unblockedRating); 355 } 356 } 357 358 /** 359 * Enables or disables the caption in this TvView. 360 * <p> 361 * Note that this method does not take any effect unless the current TvView is tuned. 362 * 363 * @param enabled {@code true} to enable, {@code false} to disable. 364 */ 365 public void setCaptionEnabled(boolean enabled) { 366 mCaptionEnabled = enabled ? CAPTION_ENABLED : CAPTION_DISABLED; 367 if (mSession != null) { 368 mSession.setCaptionEnabled(enabled); 369 } 370 } 371 372 /** 373 * Selects a track. 374 * 375 * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 376 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 377 * @param trackId The ID of the track to select. {@code null} means to unselect the current 378 * track for a given type. 379 * @see #getTracks 380 * @see #getSelectedTrack 381 */ 382 public void selectTrack(int type, String trackId) { 383 if (mSession != null) { 384 mSession.selectTrack(type, trackId); 385 } 386 } 387 388 /** 389 * Returns the list of tracks. Returns {@code null} if the information is not available. 390 * 391 * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 392 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 393 * @see #selectTrack 394 * @see #getSelectedTrack 395 */ 396 public List<TvTrackInfo> getTracks(int type) { 397 if (mSession == null) { 398 return null; 399 } 400 return mSession.getTracks(type); 401 } 402 403 /** 404 * Returns the ID of the selected track for a given type. Returns {@code null} if the 405 * information is not available or the track is not selected. 406 * 407 * @param type The type of the selected tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 408 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 409 * @see #selectTrack 410 * @see #getTracks 411 */ 412 public String getSelectedTrack(int type) { 413 if (mSession == null) { 414 return null; 415 } 416 return mSession.getSelectedTrack(type); 417 } 418 419 /** 420 * Calls {@link TvInputService.Session#appPrivateCommand(String, Bundle) 421 * TvInputService.Session.appPrivateCommand()} on the current TvView. 422 * 423 * @param action Name of the command to be performed. This <em>must</em> be a scoped name, i.e. 424 * prefixed with a package name you own, so that different developers will not create 425 * conflicting commands. 426 * @param data Any data to include with the command. 427 * @hide 428 */ 429 @SystemApi 430 public void sendAppPrivateCommand(String action, Bundle data) { 431 if (TextUtils.isEmpty(action)) { 432 throw new IllegalArgumentException("action cannot be null or an empty string"); 433 } 434 if (mSession != null) { 435 mSession.sendAppPrivateCommand(action, data); 436 } 437 } 438 439 /** 440 * Dispatches an unhandled input event to the next receiver. 441 * <p> 442 * Except system keys, TvView always consumes input events in the normal flow. This is called 443 * asynchronously from where the event is dispatched. It gives the host application a chance to 444 * dispatch the unhandled input events. 445 * 446 * @param event The input event. 447 * @return {@code true} if the event was handled by the view, {@code false} otherwise. 448 */ 449 public boolean dispatchUnhandledInputEvent(InputEvent event) { 450 if (mOnUnhandledInputEventListener != null) { 451 if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) { 452 return true; 453 } 454 } 455 return onUnhandledInputEvent(event); 456 } 457 458 /** 459 * Called when an unhandled input event also has not been handled by the user provided 460 * callback. This is the last chance to handle the unhandled input event in the TvView. 461 * 462 * @param event The input event. 463 * @return If you handled the event, return {@code true}. If you want to allow the event to be 464 * handled by the next receiver, return {@code false}. 465 */ 466 public boolean onUnhandledInputEvent(InputEvent event) { 467 return false; 468 } 469 470 /** 471 * Registers a callback to be invoked when an input event is not handled by the bound TV input. 472 * 473 * @param listener The callback to be invoked when the unhandled input event is received. 474 */ 475 public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) { 476 mOnUnhandledInputEventListener = listener; 477 } 478 479 @Override 480 public boolean dispatchKeyEvent(KeyEvent event) { 481 if (super.dispatchKeyEvent(event)) { 482 return true; 483 } 484 if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")"); 485 if (mSession == null) { 486 return false; 487 } 488 InputEvent copiedEvent = event.copy(); 489 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 490 mHandler); 491 return ret != Session.DISPATCH_NOT_HANDLED; 492 } 493 494 @Override 495 public boolean dispatchTouchEvent(MotionEvent event) { 496 if (super.dispatchTouchEvent(event)) { 497 return true; 498 } 499 if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")"); 500 if (mSession == null) { 501 return false; 502 } 503 InputEvent copiedEvent = event.copy(); 504 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 505 mHandler); 506 return ret != Session.DISPATCH_NOT_HANDLED; 507 } 508 509 @Override 510 public boolean dispatchTrackballEvent(MotionEvent event) { 511 if (super.dispatchTrackballEvent(event)) { 512 return true; 513 } 514 if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")"); 515 if (mSession == null) { 516 return false; 517 } 518 InputEvent copiedEvent = event.copy(); 519 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 520 mHandler); 521 return ret != Session.DISPATCH_NOT_HANDLED; 522 } 523 524 @Override 525 public boolean dispatchGenericMotionEvent(MotionEvent event) { 526 if (super.dispatchGenericMotionEvent(event)) { 527 return true; 528 } 529 if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")"); 530 if (mSession == null) { 531 return false; 532 } 533 InputEvent copiedEvent = event.copy(); 534 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 535 mHandler); 536 return ret != Session.DISPATCH_NOT_HANDLED; 537 } 538 539 @Override 540 public void dispatchWindowFocusChanged(boolean hasFocus) { 541 super.dispatchWindowFocusChanged(hasFocus); 542 // Other app may have shown its own main TvView. 543 // Set main again to regain main session. 544 synchronized (sMainTvViewLock) { 545 if (hasFocus && this == sMainTvView.get() && mSession != null) { 546 mSession.setMain(); 547 } 548 } 549 } 550 551 @Override 552 protected void onAttachedToWindow() { 553 super.onAttachedToWindow(); 554 createSessionOverlayView(); 555 } 556 557 @Override 558 protected void onDetachedFromWindow() { 559 removeSessionOverlayView(); 560 super.onDetachedFromWindow(); 561 } 562 563 @Override 564 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 565 if (DEBUG) { 566 Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right 567 + ", bottom=" + bottom + ",)"); 568 } 569 if (mUseRequestedSurfaceLayout) { 570 mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight, 571 mSurfaceViewBottom); 572 } else { 573 mSurfaceView.layout(0, 0, right - left, bottom - top); 574 } 575 } 576 577 @Override 578 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 579 mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec); 580 int width = mSurfaceView.getMeasuredWidth(); 581 int height = mSurfaceView.getMeasuredHeight(); 582 int childState = mSurfaceView.getMeasuredState(); 583 setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState), 584 resolveSizeAndState(height, heightMeasureSpec, 585 childState << MEASURED_HEIGHT_STATE_SHIFT)); 586 } 587 588 @Override 589 protected void onVisibilityChanged(View changedView, int visibility) { 590 super.onVisibilityChanged(changedView, visibility); 591 mSurfaceView.setVisibility(visibility); 592 if (visibility == View.VISIBLE) { 593 createSessionOverlayView(); 594 } else { 595 removeSessionOverlayView(); 596 } 597 } 598 599 private void resetSurfaceView() { 600 if (mSurfaceView != null) { 601 mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback); 602 removeView(mSurfaceView); 603 } 604 mSurface = null; 605 mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) { 606 @Override 607 protected void updateWindow(boolean force, boolean redrawNeeded) { 608 super.updateWindow(force, redrawNeeded); 609 relayoutSessionOverlayView(); 610 }}; 611 mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); 612 if (mWindowZOrder == ZORDER_MEDIA_OVERLAY) { 613 mSurfaceView.setZOrderMediaOverlay(true); 614 } else if (mWindowZOrder == ZORDER_ON_TOP) { 615 mSurfaceView.setZOrderOnTop(true); 616 } 617 addView(mSurfaceView); 618 } 619 620 private void release() { 621 setSessionSurface(null); 622 removeSessionOverlayView(); 623 mUseRequestedSurfaceLayout = false; 624 mSession.release(); 625 mSession = null; 626 mSessionCallback = null; 627 } 628 629 private void setSessionSurface(Surface surface) { 630 if (mSession == null) { 631 return; 632 } 633 mSession.setSurface(surface); 634 } 635 636 private void dispatchSurfaceChanged(int format, int width, int height) { 637 if (mSession == null) { 638 return; 639 } 640 mSession.dispatchSurfaceChanged(format, width, height); 641 } 642 643 private void createSessionOverlayView() { 644 if (mSession == null || !isAttachedToWindow() 645 || mOverlayViewCreated || mWindowZOrder != ZORDER_MEDIA) { 646 return; 647 } 648 mOverlayViewFrame = getViewFrameOnScreen(); 649 mSession.createOverlayView(this, mOverlayViewFrame); 650 mOverlayViewCreated = true; 651 } 652 653 private void removeSessionOverlayView() { 654 if (mSession == null || !mOverlayViewCreated) { 655 return; 656 } 657 mSession.removeOverlayView(); 658 mOverlayViewCreated = false; 659 mOverlayViewFrame = null; 660 } 661 662 private void relayoutSessionOverlayView() { 663 if (mSession == null || !isAttachedToWindow() || !mOverlayViewCreated 664 || mWindowZOrder != ZORDER_MEDIA) { 665 return; 666 } 667 Rect viewFrame = getViewFrameOnScreen(); 668 if (viewFrame.equals(mOverlayViewFrame)) { 669 return; 670 } 671 mSession.relayoutOverlayView(viewFrame); 672 mOverlayViewFrame = viewFrame; 673 } 674 675 private Rect getViewFrameOnScreen() { 676 int[] location = new int[2]; 677 getLocationOnScreen(location); 678 return new Rect(location[0], location[1], 679 location[0] + getWidth(), location[1] + getHeight()); 680 } 681 682 /** 683 * Interface used to receive various status updates on the {@link TvView}. 684 */ 685 public abstract static class TvInputListener { 686 687 /** 688 * This is invoked when an error occurred while establishing a connection to the underlying 689 * TV input. 690 * 691 * @param inputId The ID of the TV input bound to this view. 692 */ 693 public void onConnectionFailed(String inputId) { 694 } 695 696 /** 697 * This is invoked when the existing connection to the underlying TV input is lost. 698 * 699 * @param inputId The ID of the TV input bound to this view. 700 */ 701 public void onDisconnected(String inputId) { 702 } 703 704 /** 705 * This is invoked when the view is tuned to a specific channel and starts decoding video 706 * stream from there. It is also called later when the video size is changed. 707 * 708 * @param inputId The ID of the TV input bound to this view. 709 * @param width The width of the video. 710 * @param height The height of the video. 711 */ 712 public void onVideoSizeChanged(String inputId, int width, int height) { 713 } 714 715 /** 716 * This is invoked when the channel of this TvView is changed by the underlying TV input 717 * with out any {@link TvView#tune(String, Uri)} request. 718 * 719 * @param inputId The ID of the TV input bound to this view. 720 * @param channelUri The URI of a channel. 721 */ 722 public void onChannelRetuned(String inputId, Uri channelUri) { 723 } 724 725 /** 726 * This is called when the track information has been changed. 727 * 728 * @param inputId The ID of the TV input bound to this view. 729 * @param tracks A list which includes track information. 730 */ 731 public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) { 732 } 733 734 /** 735 * This is called when there is a change on the selected tracks. 736 * 737 * @param inputId The ID of the TV input bound to this view. 738 * @param type The type of the track selected. The type can be 739 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 740 * {@link TvTrackInfo#TYPE_SUBTITLE}. 741 * @param trackId The ID of the track selected. 742 */ 743 public void onTrackSelected(String inputId, int type, String trackId) { 744 } 745 746 /** 747 * This is called when the video is available, so the TV input starts the playback. 748 * 749 * @param inputId The ID of the TV input bound to this view. 750 */ 751 public void onVideoAvailable(String inputId) { 752 } 753 754 /** 755 * This is called when the video is not available, so the TV input stops the playback. 756 * 757 * @param inputId The ID of the TV input bound to this view. 758 * @param reason The reason why the TV input stopped the playback: 759 * <ul> 760 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 761 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} 762 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 763 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 764 * </ul> 765 */ 766 public void onVideoUnavailable(String inputId, int reason) { 767 } 768 769 /** 770 * This is called when the current program content turns out to be allowed to watch since 771 * its content rating is not blocked by parental controls. 772 * 773 * @param inputId The ID of the TV input bound to this view. 774 */ 775 public void onContentAllowed(String inputId) { 776 } 777 778 /** 779 * This is called when the current program content turns out to be not allowed to watch 780 * since its content rating is blocked by parental controls. 781 * 782 * @param inputId The ID of the TV input bound to this view. 783 * @param rating The content rating of the blocked program. 784 */ 785 public void onContentBlocked(String inputId, TvContentRating rating) { 786 } 787 788 /** 789 * This is invoked when a custom event from the bound TV input is sent to this view. 790 * 791 * @param eventType The type of the event. 792 * @param eventArgs Optional arguments of the event. 793 * @hide 794 */ 795 @SystemApi 796 public void onEvent(String inputId, String eventType, Bundle eventArgs) { 797 } 798 } 799 800 /** 801 * Interface definition for a callback to be invoked when the unhandled input event is received. 802 */ 803 public interface OnUnhandledInputEventListener { 804 /** 805 * Called when an input event was not handled by the bound TV input. 806 * <p> 807 * This is called asynchronously from where the event is dispatched. It gives the host 808 * application a chance to handle the unhandled input events. 809 * 810 * @param event The input event. 811 * @return If you handled the event, return {@code true}. If you want to allow the event to 812 * be handled by the next receiver, return {@code false}. 813 */ 814 boolean onUnhandledInputEvent(InputEvent event); 815 } 816 817 private class MySessionCallback extends SessionCallback { 818 final String mInputId; 819 Uri mChannelUri; 820 Bundle mTuneParams; 821 822 MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) { 823 mInputId = inputId; 824 mChannelUri = channelUri; 825 mTuneParams = tuneParams; 826 } 827 828 @Override 829 public void onSessionCreated(Session session) { 830 if (this != mSessionCallback) { 831 // This callback is obsolete. 832 if (session != null) { 833 session.release(); 834 } 835 return; 836 } 837 if (DEBUG) { 838 Log.d(TAG, "onSessionCreated()"); 839 } 840 mSession = session; 841 if (session != null) { 842 synchronized (sMainTvViewLock) { 843 if (hasWindowFocus() && TvView.this == sMainTvView.get()) { 844 mSession.setMain(); 845 } 846 } 847 // mSurface may not be ready yet as soon as starting an application. 848 // In the case, we don't send Session.setSurface(null) unnecessarily. 849 // setSessionSurface will be called in surfaceCreated. 850 if (mSurface != null) { 851 setSessionSurface(mSurface); 852 if (mSurfaceChanged) { 853 dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); 854 } 855 } 856 createSessionOverlayView(); 857 if (mCaptionEnabled != CAPTION_DEFAULT) { 858 mSession.setCaptionEnabled(mCaptionEnabled == CAPTION_ENABLED); 859 } 860 mSession.tune(mChannelUri, mTuneParams); 861 if (mHasStreamVolume) { 862 mSession.setStreamVolume(mStreamVolume); 863 } 864 } else { 865 if (mListener != null) { 866 mListener.onConnectionFailed(mInputId); 867 } 868 } 869 } 870 871 @Override 872 public void onSessionReleased(Session session) { 873 if (this != mSessionCallback) { 874 return; 875 } 876 mOverlayViewCreated = false; 877 mOverlayViewFrame = null; 878 mSessionCallback = null; 879 mSession = null; 880 if (mListener != null) { 881 mListener.onDisconnected(mInputId); 882 } 883 } 884 885 @Override 886 public void onChannelRetuned(Session session, Uri channelUri) { 887 if (this != mSessionCallback) { 888 return; 889 } 890 if (DEBUG) { 891 Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")"); 892 } 893 if (mListener != null) { 894 mListener.onChannelRetuned(mInputId, channelUri); 895 } 896 } 897 898 @Override 899 public void onTracksChanged(Session session, List<TvTrackInfo> tracks) { 900 if (this != mSessionCallback) { 901 return; 902 } 903 if (DEBUG) { 904 Log.d(TAG, "onTracksChanged()"); 905 } 906 if (mListener != null) { 907 mListener.onTracksChanged(mInputId, tracks); 908 } 909 } 910 911 @Override 912 public void onTrackSelected(Session session, int type, String trackId) { 913 if (this != mSessionCallback) { 914 return; 915 } 916 if (DEBUG) { 917 Log.d(TAG, "onTrackSelected()"); 918 } 919 // TODO: Update the video size when the type is TYPE_VIDEO. 920 if (mListener != null) { 921 mListener.onTrackSelected(mInputId, type, trackId); 922 } 923 } 924 925 @Override 926 public void onVideoAvailable(Session session) { 927 if (this != mSessionCallback) { 928 return; 929 } 930 if (DEBUG) { 931 Log.d(TAG, "onVideoAvailable()"); 932 } 933 if (mListener != null) { 934 mListener.onVideoAvailable(mInputId); 935 } 936 } 937 938 @Override 939 public void onVideoUnavailable(Session session, int reason) { 940 if (this != mSessionCallback) { 941 return; 942 } 943 if (DEBUG) { 944 Log.d(TAG, "onVideoUnavailable(" + reason + ")"); 945 } 946 if (mListener != null) { 947 mListener.onVideoUnavailable(mInputId, reason); 948 } 949 } 950 951 @Override 952 public void onContentAllowed(Session session) { 953 if (this != mSessionCallback) { 954 return; 955 } 956 if (DEBUG) { 957 Log.d(TAG, "onContentAllowed()"); 958 } 959 if (mListener != null) { 960 mListener.onContentAllowed(mInputId); 961 } 962 } 963 964 @Override 965 public void onContentBlocked(Session session, TvContentRating rating) { 966 if (DEBUG) { 967 Log.d(TAG, "onContentBlocked()"); 968 } 969 if (mListener != null) { 970 mListener.onContentBlocked(mInputId, rating); 971 } 972 } 973 974 @Override 975 public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { 976 if (DEBUG) { 977 Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right=" 978 + right + ", bottom=" + bottom + ",)"); 979 } 980 mSurfaceViewLeft = left; 981 mSurfaceViewTop = top; 982 mSurfaceViewRight = right; 983 mSurfaceViewBottom = bottom; 984 mUseRequestedSurfaceLayout = true; 985 requestLayout(); 986 } 987 988 @Override 989 public void onSessionEvent(Session session, String eventType, Bundle eventArgs) { 990 if (this != mSessionCallback) { 991 return; 992 } 993 if (DEBUG) { 994 Log.d(TAG, "onSessionEvent(" + eventType + ")"); 995 } 996 if (mListener != null) { 997 mListener.onEvent(mInputId, eventType, eventArgs); 998 } 999 } 1000 } 1001} 1002