1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.tv; 18 19 import android.annotation.FloatRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SystemApi; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.graphics.Canvas; 27 import android.graphics.PorterDuff; 28 import android.graphics.Rect; 29 import android.graphics.RectF; 30 import android.graphics.Region; 31 import android.media.PlaybackParams; 32 import android.media.tv.TvInputManager.Session; 33 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback; 34 import android.media.tv.TvInputManager.SessionCallback; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.text.TextUtils; 39 import android.util.AttributeSet; 40 import android.util.Log; 41 import android.util.Pair; 42 import android.view.InputEvent; 43 import android.view.KeyEvent; 44 import android.view.MotionEvent; 45 import android.view.Surface; 46 import android.view.SurfaceHolder; 47 import android.view.SurfaceView; 48 import android.view.View; 49 import android.view.ViewGroup; 50 import android.view.ViewRootImpl; 51 52 import java.lang.ref.WeakReference; 53 import java.util.ArrayDeque; 54 import java.util.List; 55 import java.util.Queue; 56 57 /** 58 * Displays TV contents. The TvView class provides a high level interface for applications to show 59 * TV programs from various TV sources that implement {@link TvInputService}. (Note that the list of 60 * TV inputs available on the system can be obtained by calling 61 * {@link TvInputManager#getTvInputList() TvInputManager.getTvInputList()}.) 62 * 63 * <p>Once the application supplies the URI for a specific TV channel to {@link #tune} 64 * method, it takes care of underlying service binding (and unbinding if the current TvView is 65 * already bound to a service) and automatically allocates/deallocates resources needed. In addition 66 * to a few essential methods to control how the contents are presented, it also provides a way to 67 * dispatch input events to the connected TvInputService in order to enable custom key actions for 68 * the TV input. 69 */ 70 public class TvView extends ViewGroup { 71 private static final String TAG = "TvView"; 72 private static final boolean DEBUG = false; 73 74 private static final int ZORDER_MEDIA = 0; 75 private static final int ZORDER_MEDIA_OVERLAY = 1; 76 private static final int ZORDER_ON_TOP = 2; 77 78 private static final WeakReference<TvView> NULL_TV_VIEW = new WeakReference<>(null); 79 80 private static final Object sMainTvViewLock = new Object(); 81 private static WeakReference<TvView> sMainTvView = NULL_TV_VIEW; 82 83 private final Handler mHandler = new Handler(); 84 private Session mSession; 85 private SurfaceView mSurfaceView; 86 private Surface mSurface; 87 private boolean mOverlayViewCreated; 88 private Rect mOverlayViewFrame; 89 private final TvInputManager mTvInputManager; 90 private MySessionCallback mSessionCallback; 91 private TvInputCallback mCallback; 92 private OnUnhandledInputEventListener mOnUnhandledInputEventListener; 93 private Float mStreamVolume; 94 private Boolean mCaptionEnabled; 95 private final Queue<Pair<String, Bundle>> mPendingAppPrivateCommands = new ArrayDeque<>(); 96 97 private boolean mSurfaceChanged; 98 private int mSurfaceFormat; 99 private int mSurfaceWidth; 100 private int mSurfaceHeight; 101 private final AttributeSet mAttrs; 102 private final int mDefStyleAttr; 103 private int mWindowZOrder; 104 private boolean mUseRequestedSurfaceLayout; 105 private int mSurfaceViewLeft; 106 private int mSurfaceViewRight; 107 private int mSurfaceViewTop; 108 private int mSurfaceViewBottom; 109 private TimeShiftPositionCallback mTimeShiftPositionCallback; 110 111 private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() { 112 @Override 113 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 114 if (DEBUG) { 115 Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format + ", width=" 116 + width + ", height=" + height + ")"); 117 } 118 mSurfaceFormat = format; 119 mSurfaceWidth = width; 120 mSurfaceHeight = height; 121 mSurfaceChanged = true; 122 dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); 123 } 124 125 @Override 126 public void surfaceCreated(SurfaceHolder holder) { 127 mSurface = holder.getSurface(); 128 setSessionSurface(mSurface); 129 } 130 131 @Override 132 public void surfaceDestroyed(SurfaceHolder holder) { 133 mSurface = null; 134 mSurfaceChanged = false; 135 setSessionSurface(null); 136 } 137 }; 138 139 private final FinishedInputEventCallback mFinishedInputEventCallback = 140 new FinishedInputEventCallback() { 141 @Override 142 public void onFinishedInputEvent(Object token, boolean handled) { 143 if (DEBUG) { 144 Log.d(TAG, "onFinishedInputEvent(token=" + token + ", handled=" + handled + ")"); 145 } 146 if (handled) { 147 return; 148 } 149 // TODO: Re-order unhandled events. 150 InputEvent event = (InputEvent) token; 151 if (dispatchUnhandledInputEvent(event)) { 152 return; 153 } 154 ViewRootImpl viewRootImpl = getViewRootImpl(); 155 if (viewRootImpl != null) { 156 viewRootImpl.dispatchUnhandledInputEvent(event); 157 } 158 } 159 }; 160 TvView(Context context)161 public TvView(Context context) { 162 this(context, null, 0); 163 } 164 TvView(Context context, AttributeSet attrs)165 public TvView(Context context, AttributeSet attrs) { 166 this(context, attrs, 0); 167 } 168 TvView(Context context, AttributeSet attrs, int defStyleAttr)169 public TvView(Context context, AttributeSet attrs, int defStyleAttr) { 170 super(context, attrs, defStyleAttr); 171 mAttrs = attrs; 172 mDefStyleAttr = defStyleAttr; 173 resetSurfaceView(); 174 mTvInputManager = (TvInputManager) getContext().getSystemService(Context.TV_INPUT_SERVICE); 175 } 176 177 /** 178 * Sets the callback to be invoked when an event is dispatched to this TvView. 179 * 180 * @param callback The callback to receive events. A value of {@code null} removes the existing 181 * callback. 182 */ setCallback(@ullable TvInputCallback callback)183 public void setCallback(@Nullable TvInputCallback callback) { 184 mCallback = callback; 185 } 186 187 /** 188 * Sets this as the main {@link TvView}. 189 * 190 * <p>The main {@link TvView} is a {@link TvView} whose corresponding TV input determines the 191 * HDMI-CEC active source device. For an HDMI port input, one of source devices that is 192 * connected to that HDMI port becomes the active source. For an HDMI-CEC logical device input, 193 * the corresponding HDMI-CEC logical device becomes the active source. For any non-HDMI input 194 * (including the tuner, composite, S-Video, etc.), the internal device (= TV itself) becomes 195 * the active source. 196 * 197 * <p>First tuned {@link TvView} becomes main automatically, and keeps to be main until either 198 * {@link #reset} is called for the main {@link TvView} or {@code setMain()} is called for other 199 * {@link TvView}. 200 * @hide 201 */ 202 @SystemApi 203 @RequiresPermission(android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE) setMain()204 public void setMain() { 205 synchronized (sMainTvViewLock) { 206 sMainTvView = new WeakReference<>(this); 207 if (hasWindowFocus() && mSession != null) { 208 mSession.setMain(); 209 } 210 } 211 } 212 213 /** 214 * Controls whether the TvView's surface is placed on top of another regular surface view in the 215 * window (but still behind the window itself). 216 * This is typically used to place overlays on top of an underlying TvView. 217 * 218 * <p>Note that this must be set before the TvView's containing window is attached to the 219 * window manager. 220 * 221 * <p>Calling this overrides any previous call to {@link #setZOrderOnTop}. 222 * 223 * @param isMediaOverlay {@code true} to be on top of another regular surface, {@code false} 224 * otherwise. 225 */ setZOrderMediaOverlay(boolean isMediaOverlay)226 public void setZOrderMediaOverlay(boolean isMediaOverlay) { 227 if (isMediaOverlay) { 228 mWindowZOrder = ZORDER_MEDIA_OVERLAY; 229 removeSessionOverlayView(); 230 } else { 231 mWindowZOrder = ZORDER_MEDIA; 232 createSessionOverlayView(); 233 } 234 if (mSurfaceView != null) { 235 // ZOrderOnTop(false) removes WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 236 // from WindowLayoutParam as well as changes window type. 237 mSurfaceView.setZOrderOnTop(false); 238 mSurfaceView.setZOrderMediaOverlay(isMediaOverlay); 239 } 240 } 241 242 /** 243 * Controls whether the TvView's surface is placed on top of its window. Normally it is placed 244 * behind the window, to allow it to (for the most part) appear to composite with the views in 245 * the hierarchy. By setting this, you cause it to be placed above the window. This means that 246 * none of the contents of the window this TvView is in will be visible on top of its surface. 247 * 248 * <p>Note that this must be set before the TvView's containing window is attached to the window 249 * manager. 250 * 251 * <p>Calling this overrides any previous call to {@link #setZOrderMediaOverlay}. 252 * 253 * @param onTop {@code true} to be on top of its window, {@code false} otherwise. 254 */ setZOrderOnTop(boolean onTop)255 public void setZOrderOnTop(boolean onTop) { 256 if (onTop) { 257 mWindowZOrder = ZORDER_ON_TOP; 258 removeSessionOverlayView(); 259 } else { 260 mWindowZOrder = ZORDER_MEDIA; 261 createSessionOverlayView(); 262 } 263 if (mSurfaceView != null) { 264 mSurfaceView.setZOrderMediaOverlay(false); 265 mSurfaceView.setZOrderOnTop(onTop); 266 } 267 } 268 269 /** 270 * Sets the relative stream volume of this TvView. 271 * 272 * <p>This method is primarily used to handle audio focus changes or mute a specific TvView when 273 * multiple views are displayed. If the method has not yet been called, the TvView assumes the 274 * default value of {@code 1.0f}. 275 * 276 * @param volume A volume value between {@code 0.0f} to {@code 1.0f}. 277 */ setStreamVolume(@loatRangefrom = 0.0, to = 1.0) float volume)278 public void setStreamVolume(@FloatRange(from = 0.0, to = 1.0) float volume) { 279 if (DEBUG) Log.d(TAG, "setStreamVolume(" + volume + ")"); 280 mStreamVolume = volume; 281 if (mSession == null) { 282 // Volume will be set once the connection has been made. 283 return; 284 } 285 mSession.setStreamVolume(volume); 286 } 287 288 /** 289 * Tunes to a given channel. 290 * 291 * @param inputId The ID of the TV input for the given channel. 292 * @param channelUri The URI of a channel. 293 */ tune(@onNull String inputId, Uri channelUri)294 public void tune(@NonNull String inputId, Uri channelUri) { 295 tune(inputId, channelUri, null); 296 } 297 298 /** 299 * Tunes to a given channel. This can be used to provide domain-specific features that are only 300 * known between certain clients and their TV inputs. 301 * 302 * @param inputId The ID of TV input for the given channel. 303 * @param channelUri The URI of a channel. 304 * @param params Domain-specific data for this tune request. Keys <em>must</em> be a scoped 305 * name, i.e. prefixed with a package name you own, so that different developers will 306 * not create conflicting keys. 307 */ tune(String inputId, Uri channelUri, Bundle params)308 public void tune(String inputId, Uri channelUri, Bundle params) { 309 if (DEBUG) Log.d(TAG, "tune(" + channelUri + ")"); 310 if (TextUtils.isEmpty(inputId)) { 311 throw new IllegalArgumentException("inputId cannot be null or an empty string"); 312 } 313 synchronized (sMainTvViewLock) { 314 if (sMainTvView.get() == null) { 315 sMainTvView = new WeakReference<>(this); 316 } 317 } 318 if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) { 319 if (mSession != null) { 320 mSession.tune(channelUri, params); 321 } else { 322 // createSession() was called but the actual session for the given inputId has not 323 // yet been created. Just replace the existing tuning params in the callback with 324 // the new ones and tune later in onSessionCreated(). It is not necessary to create 325 // a new callback because this tuning request was made on the same inputId. 326 mSessionCallback.mChannelUri = channelUri; 327 mSessionCallback.mTuneParams = params; 328 } 329 } else { 330 resetInternal(); 331 // In case createSession() is called multiple times across different inputId's before 332 // any session is created (e.g. when quickly tuning to a channel from input A and then 333 // to another channel from input B), only the callback for the last createSession() 334 // should be invoked. (The previous callbacks are simply ignored.) To do that, we create 335 // a new callback each time and keep mSessionCallback pointing to the last one. If 336 // MySessionCallback.this is different from mSessionCallback, we know that this callback 337 // is obsolete and should ignore it. 338 mSessionCallback = new MySessionCallback(inputId, channelUri, params); 339 if (mTvInputManager != null) { 340 mTvInputManager.createSession(inputId, mSessionCallback, mHandler); 341 } 342 } 343 } 344 345 /** 346 * Resets this TvView. 347 * 348 * <p>This method is primarily used to un-tune the current TvView. 349 */ reset()350 public void reset() { 351 if (DEBUG) Log.d(TAG, "reset()"); 352 synchronized (sMainTvViewLock) { 353 if (this == sMainTvView.get()) { 354 sMainTvView = NULL_TV_VIEW; 355 } 356 } 357 resetInternal(); 358 } 359 resetInternal()360 private void resetInternal() { 361 mSessionCallback = null; 362 mPendingAppPrivateCommands.clear(); 363 if (mSession != null) { 364 setSessionSurface(null); 365 removeSessionOverlayView(); 366 mUseRequestedSurfaceLayout = false; 367 mSession.release(); 368 mSession = null; 369 resetSurfaceView(); 370 } 371 } 372 373 /** 374 * Requests to unblock TV content according to the given rating. 375 * 376 * <p>This notifies TV input that blocked content is now OK to play. 377 * 378 * @param unblockedRating A TvContentRating to unblock. 379 * @see TvInputService.Session#notifyContentBlocked(TvContentRating) 380 * @removed 381 */ requestUnblockContent(TvContentRating unblockedRating)382 public void requestUnblockContent(TvContentRating unblockedRating) { 383 unblockContent(unblockedRating); 384 } 385 386 /** 387 * Requests to unblock TV content according to the given rating. 388 * 389 * <p>This notifies TV input that blocked content is now OK to play. 390 * 391 * @param unblockedRating A TvContentRating to unblock. 392 * @see TvInputService.Session#notifyContentBlocked(TvContentRating) 393 * @hide 394 */ 395 @SystemApi 396 @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) unblockContent(TvContentRating unblockedRating)397 public void unblockContent(TvContentRating unblockedRating) { 398 if (mSession != null) { 399 mSession.unblockContent(unblockedRating); 400 } 401 } 402 403 /** 404 * Enables or disables the caption in this TvView. 405 * 406 * <p>Note that this method does not take any effect unless the current TvView is tuned. 407 * 408 * @param enabled {@code true} to enable, {@code false} to disable. 409 */ setCaptionEnabled(boolean enabled)410 public void setCaptionEnabled(boolean enabled) { 411 if (DEBUG) Log.d(TAG, "setCaptionEnabled(" + enabled + ")"); 412 mCaptionEnabled = enabled; 413 if (mSession != null) { 414 mSession.setCaptionEnabled(enabled); 415 } 416 } 417 418 /** 419 * Selects a track. 420 * 421 * @param type The type of the track to select. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 422 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 423 * @param trackId The ID of the track to select. {@code null} means to unselect the current 424 * track for a given type. 425 * @see #getTracks 426 * @see #getSelectedTrack 427 */ selectTrack(int type, String trackId)428 public void selectTrack(int type, String trackId) { 429 if (mSession != null) { 430 mSession.selectTrack(type, trackId); 431 } 432 } 433 434 /** 435 * Returns the list of tracks. Returns {@code null} if the information is not available. 436 * 437 * @param type The type of the tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 438 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 439 * @see #selectTrack 440 * @see #getSelectedTrack 441 */ getTracks(int type)442 public List<TvTrackInfo> getTracks(int type) { 443 if (mSession == null) { 444 return null; 445 } 446 return mSession.getTracks(type); 447 } 448 449 /** 450 * Returns the ID of the selected track for a given type. Returns {@code null} if the 451 * information is not available or the track is not selected. 452 * 453 * @param type The type of the selected tracks. The type can be {@link TvTrackInfo#TYPE_AUDIO}, 454 * {@link TvTrackInfo#TYPE_VIDEO} or {@link TvTrackInfo#TYPE_SUBTITLE}. 455 * @see #selectTrack 456 * @see #getTracks 457 */ getSelectedTrack(int type)458 public String getSelectedTrack(int type) { 459 if (mSession == null) { 460 return null; 461 } 462 return mSession.getSelectedTrack(type); 463 } 464 465 /** 466 * Plays a given recorded TV program. 467 * 468 * @param inputId The ID of the TV input that created the given recorded program. 469 * @param recordedProgramUri The URI of a recorded program. 470 */ timeShiftPlay(String inputId, Uri recordedProgramUri)471 public void timeShiftPlay(String inputId, Uri recordedProgramUri) { 472 if (DEBUG) Log.d(TAG, "timeShiftPlay(" + recordedProgramUri + ")"); 473 if (TextUtils.isEmpty(inputId)) { 474 throw new IllegalArgumentException("inputId cannot be null or an empty string"); 475 } 476 synchronized (sMainTvViewLock) { 477 if (sMainTvView.get() == null) { 478 sMainTvView = new WeakReference<>(this); 479 } 480 } 481 if (mSessionCallback != null && TextUtils.equals(mSessionCallback.mInputId, inputId)) { 482 if (mSession != null) { 483 mSession.timeShiftPlay(recordedProgramUri); 484 } else { 485 mSessionCallback.mRecordedProgramUri = recordedProgramUri; 486 } 487 } else { 488 resetInternal(); 489 mSessionCallback = new MySessionCallback(inputId, recordedProgramUri); 490 if (mTvInputManager != null) { 491 mTvInputManager.createSession(inputId, mSessionCallback, mHandler); 492 } 493 } 494 } 495 496 /** 497 * Pauses playback. No-op if it is already paused. Call {@link #timeShiftResume} to resume. 498 */ timeShiftPause()499 public void timeShiftPause() { 500 if (mSession != null) { 501 mSession.timeShiftPause(); 502 } 503 } 504 505 /** 506 * Resumes playback. No-op if it is already resumed. Call {@link #timeShiftPause} to pause. 507 */ timeShiftResume()508 public void timeShiftResume() { 509 if (mSession != null) { 510 mSession.timeShiftResume(); 511 } 512 } 513 514 /** 515 * Seeks to a specified time position. {@code timeMs} must be equal to or greater than the start 516 * position returned by {@link TimeShiftPositionCallback#onTimeShiftStartPositionChanged} and 517 * equal to or less than the current time. 518 * 519 * @param timeMs The time position to seek to, in milliseconds since the epoch. 520 */ timeShiftSeekTo(long timeMs)521 public void timeShiftSeekTo(long timeMs) { 522 if (mSession != null) { 523 mSession.timeShiftSeekTo(timeMs); 524 } 525 } 526 527 /** 528 * Sets playback rate using {@link android.media.PlaybackParams}. 529 * 530 * @param params The playback params. 531 */ timeShiftSetPlaybackParams(@onNull PlaybackParams params)532 public void timeShiftSetPlaybackParams(@NonNull PlaybackParams params) { 533 if (mSession != null) { 534 mSession.timeShiftSetPlaybackParams(params); 535 } 536 } 537 538 /** 539 * Sets the callback to be invoked when the time shift position is changed. 540 * 541 * @param callback The callback to receive time shift position changes. A value of {@code null} 542 * removes the existing callback. 543 */ setTimeShiftPositionCallback(@ullable TimeShiftPositionCallback callback)544 public void setTimeShiftPositionCallback(@Nullable TimeShiftPositionCallback callback) { 545 mTimeShiftPositionCallback = callback; 546 ensurePositionTracking(); 547 } 548 ensurePositionTracking()549 private void ensurePositionTracking() { 550 if (mSession == null) { 551 return; 552 } 553 mSession.timeShiftEnablePositionTracking(mTimeShiftPositionCallback != null); 554 } 555 556 /** 557 * Sends a private command to the underlying TV input. This can be used to provide 558 * domain-specific features that are only known between certain clients and their TV inputs. 559 * 560 * @param action The name of the private command to send. This <em>must</em> be a scoped name, 561 * i.e. prefixed with a package name you own, so that different developers will not 562 * create conflicting commands. 563 * @param data An optional bundle to send with the command. 564 */ sendAppPrivateCommand(@onNull String action, Bundle data)565 public void sendAppPrivateCommand(@NonNull String action, Bundle data) { 566 if (TextUtils.isEmpty(action)) { 567 throw new IllegalArgumentException("action cannot be null or an empty string"); 568 } 569 if (mSession != null) { 570 mSession.sendAppPrivateCommand(action, data); 571 } else { 572 Log.w(TAG, "sendAppPrivateCommand - session not yet created (action \"" + action 573 + "\" pending)"); 574 mPendingAppPrivateCommands.add(Pair.create(action, data)); 575 } 576 } 577 578 /** 579 * Dispatches an unhandled input event to the next receiver. 580 * 581 * <p>Except system keys, TvView always consumes input events in the normal flow. This is called 582 * asynchronously from where the event is dispatched. It gives the host application a chance to 583 * dispatch the unhandled input events. 584 * 585 * @param event The input event. 586 * @return {@code true} if the event was handled by the view, {@code false} otherwise. 587 */ dispatchUnhandledInputEvent(InputEvent event)588 public boolean dispatchUnhandledInputEvent(InputEvent event) { 589 if (mOnUnhandledInputEventListener != null) { 590 if (mOnUnhandledInputEventListener.onUnhandledInputEvent(event)) { 591 return true; 592 } 593 } 594 return onUnhandledInputEvent(event); 595 } 596 597 /** 598 * Called when an unhandled input event also has not been handled by the user provided 599 * callback. This is the last chance to handle the unhandled input event in the TvView. 600 * 601 * @param event The input event. 602 * @return If you handled the event, return {@code true}. If you want to allow the event to be 603 * handled by the next receiver, return {@code false}. 604 */ onUnhandledInputEvent(InputEvent event)605 public boolean onUnhandledInputEvent(InputEvent event) { 606 return false; 607 } 608 609 /** 610 * Registers a callback to be invoked when an input event is not handled by the bound TV input. 611 * 612 * @param listener The callback to be invoked when the unhandled input event is received. 613 */ setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener)614 public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) { 615 mOnUnhandledInputEventListener = listener; 616 } 617 618 @Override dispatchKeyEvent(KeyEvent event)619 public boolean dispatchKeyEvent(KeyEvent event) { 620 if (super.dispatchKeyEvent(event)) { 621 return true; 622 } 623 if (DEBUG) Log.d(TAG, "dispatchKeyEvent(" + event + ")"); 624 if (mSession == null) { 625 return false; 626 } 627 InputEvent copiedEvent = event.copy(); 628 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 629 mHandler); 630 return ret != Session.DISPATCH_NOT_HANDLED; 631 } 632 633 @Override dispatchTouchEvent(MotionEvent event)634 public boolean dispatchTouchEvent(MotionEvent event) { 635 if (super.dispatchTouchEvent(event)) { 636 return true; 637 } 638 if (DEBUG) Log.d(TAG, "dispatchTouchEvent(" + event + ")"); 639 if (mSession == null) { 640 return false; 641 } 642 InputEvent copiedEvent = event.copy(); 643 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 644 mHandler); 645 return ret != Session.DISPATCH_NOT_HANDLED; 646 } 647 648 @Override dispatchTrackballEvent(MotionEvent event)649 public boolean dispatchTrackballEvent(MotionEvent event) { 650 if (super.dispatchTrackballEvent(event)) { 651 return true; 652 } 653 if (DEBUG) Log.d(TAG, "dispatchTrackballEvent(" + event + ")"); 654 if (mSession == null) { 655 return false; 656 } 657 InputEvent copiedEvent = event.copy(); 658 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 659 mHandler); 660 return ret != Session.DISPATCH_NOT_HANDLED; 661 } 662 663 @Override dispatchGenericMotionEvent(MotionEvent event)664 public boolean dispatchGenericMotionEvent(MotionEvent event) { 665 if (super.dispatchGenericMotionEvent(event)) { 666 return true; 667 } 668 if (DEBUG) Log.d(TAG, "dispatchGenericMotionEvent(" + event + ")"); 669 if (mSession == null) { 670 return false; 671 } 672 InputEvent copiedEvent = event.copy(); 673 int ret = mSession.dispatchInputEvent(copiedEvent, copiedEvent, mFinishedInputEventCallback, 674 mHandler); 675 return ret != Session.DISPATCH_NOT_HANDLED; 676 } 677 678 @Override dispatchWindowFocusChanged(boolean hasFocus)679 public void dispatchWindowFocusChanged(boolean hasFocus) { 680 super.dispatchWindowFocusChanged(hasFocus); 681 // Other app may have shown its own main TvView. 682 // Set main again to regain main session. 683 synchronized (sMainTvViewLock) { 684 if (hasFocus && this == sMainTvView.get() && mSession != null 685 && checkChangeHdmiCecActiveSourcePermission()) { 686 mSession.setMain(); 687 } 688 } 689 } 690 691 @Override onAttachedToWindow()692 protected void onAttachedToWindow() { 693 super.onAttachedToWindow(); 694 createSessionOverlayView(); 695 } 696 697 @Override onDetachedFromWindow()698 protected void onDetachedFromWindow() { 699 removeSessionOverlayView(); 700 super.onDetachedFromWindow(); 701 } 702 703 @Override onLayout(boolean changed, int left, int top, int right, int bottom)704 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 705 if (DEBUG) { 706 Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right 707 + ", bottom=" + bottom + ",)"); 708 } 709 if (mUseRequestedSurfaceLayout) { 710 mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight, 711 mSurfaceViewBottom); 712 } else { 713 mSurfaceView.layout(0, 0, right - left, bottom - top); 714 } 715 } 716 717 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)718 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 719 mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec); 720 int width = mSurfaceView.getMeasuredWidth(); 721 int height = mSurfaceView.getMeasuredHeight(); 722 int childState = mSurfaceView.getMeasuredState(); 723 setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState), 724 resolveSizeAndState(height, heightMeasureSpec, 725 childState << MEASURED_HEIGHT_STATE_SHIFT)); 726 } 727 728 @Override gatherTransparentRegion(Region region)729 public boolean gatherTransparentRegion(Region region) { 730 if (mWindowZOrder != ZORDER_ON_TOP) { 731 if (region != null) { 732 int width = getWidth(); 733 int height = getHeight(); 734 if (width > 0 && height > 0) { 735 int location[] = new int[2]; 736 getLocationInWindow(location); 737 int left = location[0]; 738 int top = location[1]; 739 region.op(left, top, left + width, top + height, Region.Op.UNION); 740 } 741 } 742 } 743 return super.gatherTransparentRegion(region); 744 } 745 746 @Override draw(Canvas canvas)747 public void draw(Canvas canvas) { 748 if (mWindowZOrder != ZORDER_ON_TOP) { 749 // Punch a hole so that the underlying overlay view and surface can be shown. 750 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 751 } 752 super.draw(canvas); 753 } 754 755 @Override dispatchDraw(Canvas canvas)756 protected void dispatchDraw(Canvas canvas) { 757 if (mWindowZOrder != ZORDER_ON_TOP) { 758 // Punch a hole so that the underlying overlay view and surface can be shown. 759 canvas.drawColor(0, PorterDuff.Mode.CLEAR); 760 } 761 super.dispatchDraw(canvas); 762 } 763 764 @Override onVisibilityChanged(View changedView, int visibility)765 protected void onVisibilityChanged(View changedView, int visibility) { 766 super.onVisibilityChanged(changedView, visibility); 767 mSurfaceView.setVisibility(visibility); 768 if (visibility == View.VISIBLE) { 769 createSessionOverlayView(); 770 } else { 771 removeSessionOverlayView(); 772 } 773 } 774 resetSurfaceView()775 private void resetSurfaceView() { 776 if (mSurfaceView != null) { 777 mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback); 778 removeView(mSurfaceView); 779 } 780 mSurface = null; 781 mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) { 782 @Override 783 protected void updateSurface() { 784 super.updateSurface(); 785 relayoutSessionOverlayView(); 786 }}; 787 // The surface view's content should be treated as secure all the time. 788 mSurfaceView.setSecure(true); 789 mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback); 790 if (mWindowZOrder == ZORDER_MEDIA_OVERLAY) { 791 mSurfaceView.setZOrderMediaOverlay(true); 792 } else if (mWindowZOrder == ZORDER_ON_TOP) { 793 mSurfaceView.setZOrderOnTop(true); 794 } 795 addView(mSurfaceView); 796 } 797 setSessionSurface(Surface surface)798 private void setSessionSurface(Surface surface) { 799 if (mSession == null) { 800 return; 801 } 802 mSession.setSurface(surface); 803 } 804 dispatchSurfaceChanged(int format, int width, int height)805 private void dispatchSurfaceChanged(int format, int width, int height) { 806 if (mSession == null) { 807 return; 808 } 809 mSession.dispatchSurfaceChanged(format, width, height); 810 } 811 createSessionOverlayView()812 private void createSessionOverlayView() { 813 if (mSession == null || !isAttachedToWindow() 814 || mOverlayViewCreated || mWindowZOrder != ZORDER_MEDIA) { 815 return; 816 } 817 mOverlayViewFrame = getViewFrameOnScreen(); 818 mSession.createOverlayView(this, mOverlayViewFrame); 819 mOverlayViewCreated = true; 820 } 821 removeSessionOverlayView()822 private void removeSessionOverlayView() { 823 if (mSession == null || !mOverlayViewCreated) { 824 return; 825 } 826 mSession.removeOverlayView(); 827 mOverlayViewCreated = false; 828 mOverlayViewFrame = null; 829 } 830 relayoutSessionOverlayView()831 private void relayoutSessionOverlayView() { 832 if (mSession == null || !isAttachedToWindow() || !mOverlayViewCreated 833 || mWindowZOrder != ZORDER_MEDIA) { 834 return; 835 } 836 Rect viewFrame = getViewFrameOnScreen(); 837 if (viewFrame.equals(mOverlayViewFrame)) { 838 return; 839 } 840 mSession.relayoutOverlayView(viewFrame); 841 mOverlayViewFrame = viewFrame; 842 } 843 getViewFrameOnScreen()844 private Rect getViewFrameOnScreen() { 845 Rect frame = new Rect(); 846 getGlobalVisibleRect(frame); 847 RectF frameF = new RectF(frame); 848 getMatrix().mapRect(frameF); 849 frameF.round(frame); 850 return frame; 851 } 852 checkChangeHdmiCecActiveSourcePermission()853 private boolean checkChangeHdmiCecActiveSourcePermission() { 854 return getContext().checkSelfPermission( 855 android.Manifest.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE) 856 == PackageManager.PERMISSION_GRANTED; 857 } 858 859 /** 860 * Callback used to receive time shift position changes. 861 */ 862 public abstract static class TimeShiftPositionCallback { 863 864 /** 865 * This is called when the start position for time shifting has changed. 866 * 867 * <p>The start position for time shifting indicates the earliest possible time the user can 868 * seek to. Initially this is equivalent to the time when the underlying TV input starts 869 * recording. Later it may be adjusted because there is insufficient space or the duration 870 * of recording is limited. The application must not allow the user to seek to a position 871 * earlier than the start position. 872 * 873 * <p>For playback of a recorded program initiated by {@link #timeShiftPlay(String, Uri)}, 874 * the start position is the time when playback starts. It does not change. 875 * 876 * @param inputId The ID of the TV input bound to this view. 877 * @param timeMs The start position for time shifting, in milliseconds since the epoch. 878 */ onTimeShiftStartPositionChanged(String inputId, long timeMs)879 public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { 880 } 881 882 /** 883 * This is called when the current position for time shifting has changed. 884 * 885 * <p>The current position for time shifting is the same as the current position of 886 * playback. During playback, the current position changes continuously. When paused, it 887 * does not change. 888 * 889 * <p>Note that {@code timeMs} is wall-clock time. 890 * 891 * @param inputId The ID of the TV input bound to this view. 892 * @param timeMs The current position for time shifting, in milliseconds since the epoch. 893 */ onTimeShiftCurrentPositionChanged(String inputId, long timeMs)894 public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { 895 } 896 } 897 898 /** 899 * Callback used to receive various status updates on the {@link TvView}. 900 */ 901 public abstract static class TvInputCallback { 902 903 /** 904 * This is invoked when an error occurred while establishing a connection to the underlying 905 * TV input. 906 * 907 * @param inputId The ID of the TV input bound to this view. 908 */ onConnectionFailed(String inputId)909 public void onConnectionFailed(String inputId) { 910 } 911 912 /** 913 * This is invoked when the existing connection to the underlying TV input is lost. 914 * 915 * @param inputId The ID of the TV input bound to this view. 916 */ onDisconnected(String inputId)917 public void onDisconnected(String inputId) { 918 } 919 920 /** 921 * This is invoked when the channel of this TvView is changed by the underlying TV input 922 * without any {@link TvView#tune} request. 923 * 924 * @param inputId The ID of the TV input bound to this view. 925 * @param channelUri The URI of a channel. 926 */ onChannelRetuned(String inputId, Uri channelUri)927 public void onChannelRetuned(String inputId, Uri channelUri) { 928 } 929 930 /** 931 * This is called when the track information has been changed. 932 * 933 * @param inputId The ID of the TV input bound to this view. 934 * @param tracks A list which includes track information. 935 */ onTracksChanged(String inputId, List<TvTrackInfo> tracks)936 public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) { 937 } 938 939 /** 940 * This is called when there is a change on the selected tracks. 941 * 942 * @param inputId The ID of the TV input bound to this view. 943 * @param type The type of the track selected. The type can be 944 * {@link TvTrackInfo#TYPE_AUDIO}, {@link TvTrackInfo#TYPE_VIDEO} or 945 * {@link TvTrackInfo#TYPE_SUBTITLE}. 946 * @param trackId The ID of the track selected. 947 */ onTrackSelected(String inputId, int type, String trackId)948 public void onTrackSelected(String inputId, int type, String trackId) { 949 } 950 951 /** 952 * This is invoked when the video size has been changed. It is also called when the first 953 * time video size information becomes available after this view is tuned to a specific 954 * channel. 955 * 956 * @param inputId The ID of the TV input bound to this view. 957 * @param width The width of the video. 958 * @param height The height of the video. 959 */ onVideoSizeChanged(String inputId, int width, int height)960 public void onVideoSizeChanged(String inputId, int width, int height) { 961 } 962 963 /** 964 * This is called when the video is available, so the TV input starts the playback. 965 * 966 * @param inputId The ID of the TV input bound to this view. 967 */ onVideoAvailable(String inputId)968 public void onVideoAvailable(String inputId) { 969 } 970 971 /** 972 * This is called when the video is not available, so the TV input stops the playback. 973 * 974 * @param inputId The ID of the TV input bound to this view. 975 * @param reason The reason why the TV input stopped the playback: 976 * <ul> 977 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_UNKNOWN} 978 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_TUNING} 979 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL} 980 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_BUFFERING} 981 * <li>{@link TvInputManager#VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY} 982 * </ul> 983 */ onVideoUnavailable( String inputId, @TvInputManager.VideoUnavailableReason int reason)984 public void onVideoUnavailable( 985 String inputId, @TvInputManager.VideoUnavailableReason int reason) { 986 } 987 988 /** 989 * This is called when the current program content turns out to be allowed to watch since 990 * its content rating is not blocked by parental controls. 991 * 992 * @param inputId The ID of the TV input bound to this view. 993 */ onContentAllowed(String inputId)994 public void onContentAllowed(String inputId) { 995 } 996 997 /** 998 * This is called when the current program content turns out to be not allowed to watch 999 * since its content rating is blocked by parental controls. 1000 * 1001 * @param inputId The ID of the TV input bound to this view. 1002 * @param rating The content rating of the blocked program. 1003 */ onContentBlocked(String inputId, TvContentRating rating)1004 public void onContentBlocked(String inputId, TvContentRating rating) { 1005 } 1006 1007 /** 1008 * This is invoked when a custom event from the bound TV input is sent to this view. 1009 * 1010 * @param inputId The ID of the TV input bound to this view. 1011 * @param eventType The type of the event. 1012 * @param eventArgs Optional arguments of the event. 1013 * @hide 1014 */ 1015 @SystemApi onEvent(String inputId, String eventType, Bundle eventArgs)1016 public void onEvent(String inputId, String eventType, Bundle eventArgs) { 1017 } 1018 1019 /** 1020 * This is called when the time shift status is changed. 1021 * 1022 * @param inputId The ID of the TV input bound to this view. 1023 * @param status The current time shift status. Should be one of the followings. 1024 * <ul> 1025 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNSUPPORTED} 1026 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_UNAVAILABLE} 1027 * <li>{@link TvInputManager#TIME_SHIFT_STATUS_AVAILABLE} 1028 * </ul> 1029 */ onTimeShiftStatusChanged( String inputId, @TvInputManager.TimeShiftStatus int status)1030 public void onTimeShiftStatusChanged( 1031 String inputId, @TvInputManager.TimeShiftStatus int status) { 1032 } 1033 } 1034 1035 /** 1036 * Interface definition for a callback to be invoked when the unhandled input event is received. 1037 */ 1038 public interface OnUnhandledInputEventListener { 1039 /** 1040 * Called when an input event was not handled by the bound TV input. 1041 * 1042 * <p>This is called asynchronously from where the event is dispatched. It gives the host 1043 * application a chance to handle the unhandled input events. 1044 * 1045 * @param event The input event. 1046 * @return If you handled the event, return {@code true}. If you want to allow the event to 1047 * be handled by the next receiver, return {@code false}. 1048 */ onUnhandledInputEvent(InputEvent event)1049 boolean onUnhandledInputEvent(InputEvent event); 1050 } 1051 1052 private class MySessionCallback extends SessionCallback { 1053 final String mInputId; 1054 Uri mChannelUri; 1055 Bundle mTuneParams; 1056 Uri mRecordedProgramUri; 1057 MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams)1058 MySessionCallback(String inputId, Uri channelUri, Bundle tuneParams) { 1059 mInputId = inputId; 1060 mChannelUri = channelUri; 1061 mTuneParams = tuneParams; 1062 } 1063 MySessionCallback(String inputId, Uri recordedProgramUri)1064 MySessionCallback(String inputId, Uri recordedProgramUri) { 1065 mInputId = inputId; 1066 mRecordedProgramUri = recordedProgramUri; 1067 } 1068 1069 @Override onSessionCreated(Session session)1070 public void onSessionCreated(Session session) { 1071 if (DEBUG) { 1072 Log.d(TAG, "onSessionCreated()"); 1073 } 1074 if (this != mSessionCallback) { 1075 Log.w(TAG, "onSessionCreated - session already created"); 1076 // This callback is obsolete. 1077 if (session != null) { 1078 session.release(); 1079 } 1080 return; 1081 } 1082 mSession = session; 1083 if (session != null) { 1084 // Sends the pending app private commands first. 1085 for (Pair<String, Bundle> command : mPendingAppPrivateCommands) { 1086 mSession.sendAppPrivateCommand(command.first, command.second); 1087 } 1088 mPendingAppPrivateCommands.clear(); 1089 1090 synchronized (sMainTvViewLock) { 1091 if (hasWindowFocus() && TvView.this == sMainTvView.get() 1092 && checkChangeHdmiCecActiveSourcePermission()) { 1093 mSession.setMain(); 1094 } 1095 } 1096 // mSurface may not be ready yet as soon as starting an application. 1097 // In the case, we don't send Session.setSurface(null) unnecessarily. 1098 // setSessionSurface will be called in surfaceCreated. 1099 if (mSurface != null) { 1100 setSessionSurface(mSurface); 1101 if (mSurfaceChanged) { 1102 dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight); 1103 } 1104 } 1105 createSessionOverlayView(); 1106 if (mStreamVolume != null) { 1107 mSession.setStreamVolume(mStreamVolume); 1108 } 1109 if (mCaptionEnabled != null) { 1110 mSession.setCaptionEnabled(mCaptionEnabled); 1111 } 1112 if (mChannelUri != null) { 1113 mSession.tune(mChannelUri, mTuneParams); 1114 } else { 1115 mSession.timeShiftPlay(mRecordedProgramUri); 1116 } 1117 ensurePositionTracking(); 1118 } else { 1119 mSessionCallback = null; 1120 if (mCallback != null) { 1121 mCallback.onConnectionFailed(mInputId); 1122 } 1123 } 1124 } 1125 1126 @Override onSessionReleased(Session session)1127 public void onSessionReleased(Session session) { 1128 if (DEBUG) { 1129 Log.d(TAG, "onSessionReleased()"); 1130 } 1131 if (this != mSessionCallback) { 1132 Log.w(TAG, "onSessionReleased - session not created"); 1133 return; 1134 } 1135 mOverlayViewCreated = false; 1136 mOverlayViewFrame = null; 1137 mSessionCallback = null; 1138 mSession = null; 1139 if (mCallback != null) { 1140 mCallback.onDisconnected(mInputId); 1141 } 1142 } 1143 1144 @Override onChannelRetuned(Session session, Uri channelUri)1145 public void onChannelRetuned(Session session, Uri channelUri) { 1146 if (DEBUG) { 1147 Log.d(TAG, "onChannelChangedByTvInput(" + channelUri + ")"); 1148 } 1149 if (this != mSessionCallback) { 1150 Log.w(TAG, "onChannelRetuned - session not created"); 1151 return; 1152 } 1153 if (mCallback != null) { 1154 mCallback.onChannelRetuned(mInputId, channelUri); 1155 } 1156 } 1157 1158 @Override onTracksChanged(Session session, List<TvTrackInfo> tracks)1159 public void onTracksChanged(Session session, List<TvTrackInfo> tracks) { 1160 if (DEBUG) { 1161 Log.d(TAG, "onTracksChanged(" + tracks + ")"); 1162 } 1163 if (this != mSessionCallback) { 1164 Log.w(TAG, "onTracksChanged - session not created"); 1165 return; 1166 } 1167 if (mCallback != null) { 1168 mCallback.onTracksChanged(mInputId, tracks); 1169 } 1170 } 1171 1172 @Override onTrackSelected(Session session, int type, String trackId)1173 public void onTrackSelected(Session session, int type, String trackId) { 1174 if (DEBUG) { 1175 Log.d(TAG, "onTrackSelected(type=" + type + ", trackId=" + trackId + ")"); 1176 } 1177 if (this != mSessionCallback) { 1178 Log.w(TAG, "onTrackSelected - session not created"); 1179 return; 1180 } 1181 if (mCallback != null) { 1182 mCallback.onTrackSelected(mInputId, type, trackId); 1183 } 1184 } 1185 1186 @Override onVideoSizeChanged(Session session, int width, int height)1187 public void onVideoSizeChanged(Session session, int width, int height) { 1188 if (DEBUG) { 1189 Log.d(TAG, "onVideoSizeChanged()"); 1190 } 1191 if (this != mSessionCallback) { 1192 Log.w(TAG, "onVideoSizeChanged - session not created"); 1193 return; 1194 } 1195 if (mCallback != null) { 1196 mCallback.onVideoSizeChanged(mInputId, width, height); 1197 } 1198 } 1199 1200 @Override onVideoAvailable(Session session)1201 public void onVideoAvailable(Session session) { 1202 if (DEBUG) { 1203 Log.d(TAG, "onVideoAvailable()"); 1204 } 1205 if (this != mSessionCallback) { 1206 Log.w(TAG, "onVideoAvailable - session not created"); 1207 return; 1208 } 1209 if (mCallback != null) { 1210 mCallback.onVideoAvailable(mInputId); 1211 } 1212 } 1213 1214 @Override onVideoUnavailable(Session session, int reason)1215 public void onVideoUnavailable(Session session, int reason) { 1216 if (DEBUG) { 1217 Log.d(TAG, "onVideoUnavailable(reason=" + reason + ")"); 1218 } 1219 if (this != mSessionCallback) { 1220 Log.w(TAG, "onVideoUnavailable - session not created"); 1221 return; 1222 } 1223 if (mCallback != null) { 1224 mCallback.onVideoUnavailable(mInputId, reason); 1225 } 1226 } 1227 1228 @Override onContentAllowed(Session session)1229 public void onContentAllowed(Session session) { 1230 if (DEBUG) { 1231 Log.d(TAG, "onContentAllowed()"); 1232 } 1233 if (this != mSessionCallback) { 1234 Log.w(TAG, "onContentAllowed - session not created"); 1235 return; 1236 } 1237 if (mCallback != null) { 1238 mCallback.onContentAllowed(mInputId); 1239 } 1240 } 1241 1242 @Override onContentBlocked(Session session, TvContentRating rating)1243 public void onContentBlocked(Session session, TvContentRating rating) { 1244 if (DEBUG) { 1245 Log.d(TAG, "onContentBlocked(rating=" + rating + ")"); 1246 } 1247 if (this != mSessionCallback) { 1248 Log.w(TAG, "onContentBlocked - session not created"); 1249 return; 1250 } 1251 if (mCallback != null) { 1252 mCallback.onContentBlocked(mInputId, rating); 1253 } 1254 } 1255 1256 @Override onLayoutSurface(Session session, int left, int top, int right, int bottom)1257 public void onLayoutSurface(Session session, int left, int top, int right, int bottom) { 1258 if (DEBUG) { 1259 Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right=" 1260 + right + ", bottom=" + bottom + ",)"); 1261 } 1262 if (this != mSessionCallback) { 1263 Log.w(TAG, "onLayoutSurface - session not created"); 1264 return; 1265 } 1266 mSurfaceViewLeft = left; 1267 mSurfaceViewTop = top; 1268 mSurfaceViewRight = right; 1269 mSurfaceViewBottom = bottom; 1270 mUseRequestedSurfaceLayout = true; 1271 requestLayout(); 1272 } 1273 1274 @Override onSessionEvent(Session session, String eventType, Bundle eventArgs)1275 public void onSessionEvent(Session session, String eventType, Bundle eventArgs) { 1276 if (DEBUG) { 1277 Log.d(TAG, "onSessionEvent(" + eventType + ")"); 1278 } 1279 if (this != mSessionCallback) { 1280 Log.w(TAG, "onSessionEvent - session not created"); 1281 return; 1282 } 1283 if (mCallback != null) { 1284 mCallback.onEvent(mInputId, eventType, eventArgs); 1285 } 1286 } 1287 1288 @Override onTimeShiftStatusChanged(Session session, int status)1289 public void onTimeShiftStatusChanged(Session session, int status) { 1290 if (DEBUG) { 1291 Log.d(TAG, "onTimeShiftStatusChanged()"); 1292 } 1293 if (this != mSessionCallback) { 1294 Log.w(TAG, "onTimeShiftStatusChanged - session not created"); 1295 return; 1296 } 1297 if (mCallback != null) { 1298 mCallback.onTimeShiftStatusChanged(mInputId, status); 1299 } 1300 } 1301 1302 @Override onTimeShiftStartPositionChanged(Session session, long timeMs)1303 public void onTimeShiftStartPositionChanged(Session session, long timeMs) { 1304 if (DEBUG) { 1305 Log.d(TAG, "onTimeShiftStartPositionChanged()"); 1306 } 1307 if (this != mSessionCallback) { 1308 Log.w(TAG, "onTimeShiftStartPositionChanged - session not created"); 1309 return; 1310 } 1311 if (mTimeShiftPositionCallback != null) { 1312 mTimeShiftPositionCallback.onTimeShiftStartPositionChanged(mInputId, timeMs); 1313 } 1314 } 1315 1316 @Override onTimeShiftCurrentPositionChanged(Session session, long timeMs)1317 public void onTimeShiftCurrentPositionChanged(Session session, long timeMs) { 1318 if (DEBUG) { 1319 Log.d(TAG, "onTimeShiftCurrentPositionChanged()"); 1320 } 1321 if (this != mSessionCallback) { 1322 Log.w(TAG, "onTimeShiftCurrentPositionChanged - session not created"); 1323 return; 1324 } 1325 if (mTimeShiftPositionCallback != null) { 1326 mTimeShiftPositionCallback.onTimeShiftCurrentPositionChanged(mInputId, timeMs); 1327 } 1328 } 1329 } 1330 } 1331