1 /* 2 * Copyright (C) 2015 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 com.android.tv.ui; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.TimeInterpolator; 22 import android.app.Activity; 23 import android.content.Context; 24 import android.content.pm.PackageManager; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.graphics.PorterDuff; 28 import android.graphics.drawable.BitmapDrawable; 29 import android.graphics.drawable.Drawable; 30 import android.media.PlaybackParams; 31 import android.media.tv.TvContentRating; 32 import android.media.tv.TvInputInfo; 33 import android.media.tv.TvInputManager; 34 import android.media.tv.TvTrackInfo; 35 import android.media.tv.TvView; 36 import android.media.tv.TvView.OnUnhandledInputEventListener; 37 import android.net.ConnectivityManager; 38 import android.net.Uri; 39 import android.os.AsyncTask; 40 import android.os.Build; 41 import android.os.Bundle; 42 import android.support.annotation.IntDef; 43 import android.support.annotation.NonNull; 44 import android.support.annotation.Nullable; 45 import android.support.annotation.VisibleForTesting; 46 import android.text.TextUtils; 47 import android.text.format.DateUtils; 48 import android.util.AttributeSet; 49 import android.util.Log; 50 import android.view.KeyEvent; 51 import android.view.MotionEvent; 52 import android.view.SurfaceView; 53 import android.view.View; 54 import android.view.accessibility.AccessibilityManager; 55 import android.widget.FrameLayout; 56 import android.widget.ImageView; 57 58 import com.android.tv.InputSessionManager; 59 import com.android.tv.InputSessionManager.TvViewSession; 60 import com.android.tv.R; 61 import com.android.tv.TvSingletons; 62 import com.android.tv.analytics.Tracker; 63 import com.android.tv.common.BuildConfig; 64 import com.android.tv.common.CommonConstants; 65 import com.android.tv.common.compat.TvInputConstantCompat; 66 import com.android.tv.common.compat.TvViewCompat.TvInputCallbackCompat; 67 import com.android.tv.common.feature.CommonFeatures; 68 import com.android.tv.common.util.CommonUtils; 69 import com.android.tv.common.util.Debug; 70 import com.android.tv.common.util.DurationTimer; 71 import com.android.tv.common.util.PermissionUtils; 72 import com.android.tv.data.ProgramDataManager; 73 import com.android.tv.data.StreamInfo; 74 import com.android.tv.data.WatchedHistoryManager; 75 import com.android.tv.data.api.Channel; 76 import com.android.tv.data.api.Program; 77 import com.android.tv.features.TvFeatures; 78 import com.android.tv.parental.ContentRatingsManager; 79 import com.android.tv.parental.ParentalControlSettings; 80 import com.android.tv.recommendation.NotificationService; 81 import com.android.tv.ui.api.TunableTvViewPlayingApi; 82 import com.android.tv.util.NetworkUtils; 83 import com.android.tv.util.TvInputManagerHelper; 84 import com.android.tv.util.Utils; 85 import com.android.tv.util.images.ImageLoader; 86 87 import com.android.tv.common.flags.LegacyFlags; 88 89 import java.lang.annotation.Retention; 90 import java.lang.annotation.RetentionPolicy; 91 import java.util.List; 92 93 /** Includes the real {@link AppLayerTvView} handling tuning, block and other display events. */ 94 public class TunableTvView extends FrameLayout implements StreamInfo, TunableTvViewPlayingApi { 95 private static final boolean DEBUG = false; 96 private static final String TAG = "TunableTvView"; 97 98 public static final int VIDEO_UNAVAILABLE_REASON_NOT_TUNED = -1; 99 public static final int VIDEO_UNAVAILABLE_REASON_NO_RESOURCE = -2; 100 public static final int VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED = -3; 101 public static final int VIDEO_UNAVAILABLE_REASON_NONE = -100; 102 private final AccessibilityManager mAccessibilityManager; 103 104 @Retention(RetentionPolicy.SOURCE) 105 @IntDef({BLOCK_SCREEN_TYPE_NO_UI, BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW, BLOCK_SCREEN_TYPE_NORMAL}) 106 public @interface BlockScreenType {} 107 108 public static final int BLOCK_SCREEN_TYPE_NO_UI = 0; 109 public static final int BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW = 1; 110 public static final int BLOCK_SCREEN_TYPE_NORMAL = 2; 111 112 private static final String PERMISSION_RECEIVE_INPUT_EVENT = 113 CommonConstants.BASE_PACKAGE + ".permission.RECEIVE_INPUT_EVENT"; 114 115 @Retention(RetentionPolicy.SOURCE) 116 @IntDef({ 117 TIME_SHIFT_STATE_NONE, 118 TIME_SHIFT_STATE_PLAY, 119 TIME_SHIFT_STATE_PAUSE, 120 TIME_SHIFT_STATE_REWIND, 121 TIME_SHIFT_STATE_FAST_FORWARD 122 }) 123 private @interface TimeShiftState {} 124 125 private static final int TIME_SHIFT_STATE_NONE = 0; 126 private static final int TIME_SHIFT_STATE_PLAY = 1; 127 private static final int TIME_SHIFT_STATE_PAUSE = 2; 128 private static final int TIME_SHIFT_STATE_REWIND = 3; 129 private static final int TIME_SHIFT_STATE_FAST_FORWARD = 4; 130 131 private static final int FADED_IN = 0; 132 private static final int FADED_OUT = 1; 133 private static final int FADING_IN = 2; 134 private static final int FADING_OUT = 3; 135 136 private AppLayerTvView mTvView; 137 private TvViewSession mTvViewSession; 138 @Nullable private Channel mCurrentChannel; 139 private TvInputManagerHelper mInputManagerHelper; 140 private ContentRatingsManager mContentRatingsManager; 141 private ParentalControlSettings mParentalControlSettings; 142 private ProgramDataManager mProgramDataManager; 143 @Nullable private WatchedHistoryManager mWatchedHistoryManager; 144 private boolean mStarted; 145 private String mTagetInputId; 146 private TvInputInfo mInputInfo; 147 private OnTuneListener mOnTuneListener; 148 private int mVideoWidth; 149 private int mVideoHeight; 150 private int mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 151 private float mVideoFrameRate; 152 private float mVideoDisplayAspectRatio; 153 private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 154 private boolean mHasClosedCaption = false; 155 private boolean mScreenBlocked; 156 private OnScreenBlockingChangedListener mOnScreenBlockedListener; 157 private TvContentRating mBlockedContentRating; 158 private int mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED; 159 private boolean mCanReceiveInputEvent; 160 private boolean mIsMuted; 161 private float mVolume; 162 private boolean mParentControlEnabled; 163 private int mFixedSurfaceWidth; 164 private int mFixedSurfaceHeight; 165 private final boolean mCanModifyParentalControls; 166 private boolean mIsUnderShrunken; 167 168 @TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE; 169 private TimeShiftListener mTimeShiftListener; 170 private boolean mTimeShiftAvailable; 171 private long mTimeShiftCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 172 173 private final Tracker mTracker; 174 private final DurationTimer mChannelViewTimer = new DurationTimer(); 175 private InternetCheckTask mInternetCheckTask; 176 177 // A block screen view to hide the real TV view underlying. It may be used to enforce parental 178 // control, or hide screen when there's no video available and show appropriate information. 179 private final BlockScreenView mBlockScreenView; 180 private final int mTuningImageColorFilter; 181 182 // A spinner view to show buffering status. 183 private final View mBufferingSpinnerView; 184 185 private final View mDimScreenView; 186 187 private int mFadeState = FADED_IN; 188 private Runnable mActionAfterFade; 189 190 @BlockScreenType private int mBlockScreenType; 191 192 private final TvInputManagerHelper mInputManager; 193 private final ConnectivityManager mConnectivityManager; 194 private final InputSessionManager mInputSessionManager; 195 196 private int mChannelSignalStrength; 197 198 private final TvInputCallbackCompat mCallback = 199 new TvInputCallbackCompat() { 200 @Override 201 public void onConnectionFailed(String inputId) { 202 Log.w(TAG, "Failed to bind an input"); 203 mTracker.sendInputConnectionFailure(inputId); 204 Channel channel = mCurrentChannel; 205 mCurrentChannel = null; 206 mInputInfo = null; 207 mCanReceiveInputEvent = false; 208 if (mOnTuneListener != null) { 209 // If tune is called inside onTuneFailed, mOnTuneListener will be set to 210 // a new instance. In order to avoid to clear the new mOnTuneListener, 211 // we copy mOnTuneListener to l and clear mOnTuneListener before 212 // calling onTuneFailed. 213 OnTuneListener listener = mOnTuneListener; 214 mOnTuneListener = null; 215 listener.onTuneFailed(channel); 216 } 217 } 218 219 @Override 220 public void onDisconnected(String inputId) { 221 Log.w(TAG, "Session is released by crash"); 222 mTracker.sendInputDisconnected(inputId); 223 Channel channel = mCurrentChannel; 224 mCurrentChannel = null; 225 mInputInfo = null; 226 mCanReceiveInputEvent = false; 227 if (mOnTuneListener != null) { 228 OnTuneListener listener = mOnTuneListener; 229 mOnTuneListener = null; 230 listener.onUnexpectedStop(channel); 231 } 232 } 233 234 @Override 235 public void onChannelRetuned(String inputId, Uri channelUri) { 236 if (DEBUG) { 237 Log.d( 238 TAG, 239 "onChannelRetuned(inputId=" 240 + inputId 241 + ", channelUri=" 242 + channelUri 243 + ")"); 244 } 245 if (mOnTuneListener != null) { 246 mOnTuneListener.onChannelRetuned(channelUri); 247 } 248 } 249 250 @Override 251 public void onTracksChanged(String inputId, List<TvTrackInfo> tracks) { 252 mHasClosedCaption = false; 253 for (TvTrackInfo track : tracks) { 254 if (track.getType() == TvTrackInfo.TYPE_SUBTITLE) { 255 mHasClosedCaption = true; 256 break; 257 } 258 } 259 if (mOnTuneListener != null) { 260 mOnTuneListener.onStreamInfoChanged(TunableTvView.this, true); 261 } 262 } 263 264 @Override 265 public void onTrackSelected(String inputId, int type, String trackId) { 266 if (trackId == null) { 267 // A track is unselected. 268 if (type == TvTrackInfo.TYPE_VIDEO) { 269 mVideoWidth = 0; 270 mVideoHeight = 0; 271 mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 272 mVideoFrameRate = 0f; 273 mVideoDisplayAspectRatio = 0f; 274 } else if (type == TvTrackInfo.TYPE_AUDIO) { 275 mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 276 } 277 } else { 278 List<TvTrackInfo> tracks = getTracks(type); 279 boolean trackFound = false; 280 if (tracks != null) { 281 for (TvTrackInfo track : tracks) { 282 if (track.getId().equals(trackId)) { 283 if (type == TvTrackInfo.TYPE_VIDEO) { 284 mVideoWidth = track.getVideoWidth(); 285 mVideoHeight = track.getVideoHeight(); 286 mVideoFormat = 287 Utils.getVideoDefinitionLevelFromSize( 288 mVideoWidth, mVideoHeight); 289 mVideoFrameRate = track.getVideoFrameRate(); 290 if (mVideoWidth <= 0 || mVideoHeight <= 0) { 291 mVideoDisplayAspectRatio = 0.0f; 292 } else { 293 float videoPixelAspectRatio = 294 track.getVideoPixelAspectRatio(); 295 mVideoDisplayAspectRatio = (float) mVideoWidth 296 / mVideoHeight; 297 mVideoDisplayAspectRatio *= videoPixelAspectRatio > 0 ? 298 videoPixelAspectRatio : 1; 299 } 300 } else if (type == TvTrackInfo.TYPE_AUDIO) { 301 mAudioChannelCount = track.getAudioChannelCount(); 302 } 303 trackFound = true; 304 break; 305 } 306 } 307 } 308 if (!trackFound) { 309 Log.w(TAG, "Invalid track ID: " + trackId); 310 } 311 } 312 if (mOnTuneListener != null) { 313 // should not change audio track automatically when an audio track or a 314 // subtitle track is selected 315 mOnTuneListener.onStreamInfoChanged( 316 TunableTvView.this, type == TvTrackInfo.TYPE_VIDEO); 317 } 318 } 319 320 @Override 321 public void onVideoAvailable(String inputId) { 322 if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}"); 323 Debug.getTimer(Debug.TAG_START_UP_TIMER) 324 .log( 325 "Start up of TV app ends," 326 + " TunableTvView.onVideoAvailable resets timer"); 327 Debug.getTimer(Debug.TAG_START_UP_TIMER).reset(); 328 Debug.removeTimer(Debug.TAG_START_UP_TIMER); 329 mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE; 330 updateBlockScreenAndMuting(); 331 if (mOnTuneListener != null) { 332 mOnTuneListener.onStreamInfoChanged(TunableTvView.this, true); 333 } 334 } 335 336 @Override 337 public void onVideoUnavailable(String inputId, int reason) { 338 if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING 339 && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { 340 Debug.getTimer(Debug.TAG_START_UP_TIMER) 341 .log( 342 "TunableTvView.onVideoUnAvailable reason = (" 343 + reason 344 + ") and removes timer"); 345 Debug.removeTimer(Debug.TAG_START_UP_TIMER); 346 } else { 347 Debug.getTimer(Debug.TAG_START_UP_TIMER) 348 .log("TunableTvView.onVideoUnAvailable reason = (" + reason + ")"); 349 } 350 mVideoUnavailableReason = reason; 351 if (closePipIfNeeded()) { 352 return; 353 } 354 updateBlockScreenAndMuting(); 355 if (mOnTuneListener != null) { 356 mOnTuneListener.onStreamInfoChanged(TunableTvView.this, true); 357 } 358 switch (reason) { 359 case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN: 360 case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: 361 case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: 362 case CommonConstants.VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED: 363 mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason); 364 break; 365 default: 366 // do nothing 367 } 368 } 369 370 @Override 371 public void onContentAllowed(String inputId) { 372 mBlockedContentRating = null; 373 updateBlockScreenAndMuting(); 374 if (mOnTuneListener != null) { 375 mOnTuneListener.onContentAllowed(); 376 } 377 } 378 379 @Override 380 public void onContentBlocked(String inputId, TvContentRating rating) { 381 if (rating != null && rating.equals(mBlockedContentRating)) { 382 return; 383 } 384 mBlockedContentRating = rating; 385 if (closePipIfNeeded()) { 386 return; 387 } 388 updateBlockScreenAndMuting(); 389 if (mOnTuneListener != null) { 390 mOnTuneListener.onContentBlocked(); 391 } 392 } 393 394 @Override 395 public void onTimeShiftStatusChanged(String inputId, int status) { 396 if (DEBUG) { 397 Log.d( 398 TAG, 399 "onTimeShiftStatusChanged: {inputId=" 400 + inputId 401 + ", status=" 402 + status 403 + "}"); 404 } 405 boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE; 406 setTimeShiftAvailable(available); 407 } 408 409 @Override 410 public void onSignalStrength(String inputId, int value) { 411 mChannelSignalStrength = value; 412 if (mOnTuneListener != null) { 413 mOnTuneListener.onChannelSignalStrength(); 414 } 415 } 416 }; 417 TunableTvView(Context context)418 public TunableTvView(Context context) { 419 this(context, null); 420 } 421 TunableTvView(Context context, AttributeSet attrs)422 public TunableTvView(Context context, AttributeSet attrs) { 423 this(context, attrs, 0); 424 } 425 TunableTvView(Context context, AttributeSet attrs, int defStyleAttr)426 public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr) { 427 this(context, attrs, defStyleAttr, 0); 428 } 429 TunableTvView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)430 public TunableTvView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 431 super(context, attrs, defStyleAttr, defStyleRes); 432 inflate(getContext(), R.layout.tunable_tv_view, this); 433 434 TvSingletons tvSingletons = TvSingletons.getSingletons(context); 435 if (CommonFeatures.DVR.isEnabled(context)) { 436 mInputSessionManager = tvSingletons.getInputSessionManager(); 437 } else { 438 mInputSessionManager = null; 439 } 440 mInputManager = tvSingletons.getTvInputManagerHelper(); 441 mConnectivityManager = 442 (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 443 mCanModifyParentalControls = PermissionUtils.hasModifyParentalControls(context); 444 mTracker = tvSingletons.getTracker(); 445 mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL; 446 mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen); 447 mBlockScreenView.addInfoFadeInAnimationListener( 448 new AnimatorListenerAdapter() { 449 @Override 450 public void onAnimationStart(Animator animation) { 451 adjustBlockScreenSpacingAndText(); 452 } 453 }); 454 455 mBufferingSpinnerView = findViewById(R.id.buffering_spinner); 456 mTuningImageColorFilter = 457 getResources().getColor(R.color.tvview_block_image_color_filter, null); 458 mDimScreenView = findViewById(R.id.dim_screen); 459 mDimScreenView 460 .animate() 461 .setListener( 462 new AnimatorListenerAdapter() { 463 @Override 464 public void onAnimationEnd(Animator animation) { 465 if (mActionAfterFade != null) { 466 mActionAfterFade.run(); 467 } 468 } 469 470 @Override 471 public void onAnimationCancel(Animator animation) { 472 if (mActionAfterFade != null) { 473 mActionAfterFade.run(); 474 } 475 } 476 }); 477 mAccessibilityManager = context.getSystemService(AccessibilityManager.class); 478 } 479 initialize( ProgramDataManager programDataManager, TvInputManagerHelper tvInputManagerHelper, LegacyFlags mLegacyFlags)480 public void initialize( 481 ProgramDataManager programDataManager, 482 TvInputManagerHelper tvInputManagerHelper, 483 LegacyFlags mLegacyFlags) { 484 mTvView = findViewById(R.id.tv_view); 485 mTvView.setUseSecureSurface(!BuildConfig.ENG && !mLegacyFlags.enableDeveloperFeatures()); 486 487 mProgramDataManager = programDataManager; 488 mInputManagerHelper = tvInputManagerHelper; 489 mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager(); 490 mParentalControlSettings = tvInputManagerHelper.getParentalControlSettings(); 491 if (mInputSessionManager != null) { 492 mTvViewSession = mInputSessionManager.createTvViewSession(mTvView, this, mCallback); 493 } else { 494 mTvView.setCallback(mCallback); 495 } 496 } 497 start()498 public void start() { 499 mStarted = true; 500 } 501 502 /** Warms up the input to reduce the start time. */ warmUpInput(String inputId, Uri channelUri)503 public void warmUpInput(String inputId, Uri channelUri) { 504 if (!mStarted && inputId != null && channelUri != null) { 505 if (mTvViewSession != null) { 506 mTvViewSession.tune(inputId, channelUri); 507 } else { 508 mTvView.tune(inputId, channelUri); 509 } 510 mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING; 511 updateBlockScreenAndMuting(); 512 } 513 } 514 stop()515 public void stop() { 516 if (!mStarted) { 517 return; 518 } 519 mStarted = false; 520 if (mCurrentChannel != null) { 521 long duration = mChannelViewTimer.reset(); 522 mTracker.sendChannelViewStop(mCurrentChannel, duration); 523 if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) { 524 mWatchedHistoryManager.logChannelViewStop( 525 mCurrentChannel, System.currentTimeMillis(), duration); 526 } 527 } 528 reset(); 529 } 530 531 /** Releases the resources. */ release()532 public void release() { 533 if (mInputSessionManager != null) { 534 mInputSessionManager.releaseTvViewSession(mTvViewSession); 535 mTvViewSession = null; 536 } 537 } 538 539 /** Resets TV view. */ reset()540 public void reset() { 541 resetInternal(); 542 mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED; 543 updateBlockScreenAndMuting(); 544 } 545 546 /** Resets TV view to acquire the recording session. */ resetByRecording()547 public void resetByRecording() { 548 resetInternal(); 549 } 550 resetInternal()551 private void resetInternal() { 552 if (mTvViewSession != null) { 553 mTvViewSession.reset(); 554 } else { 555 mTvView.reset(); 556 } 557 mCurrentChannel = null; 558 mInputInfo = null; 559 mCanReceiveInputEvent = false; 560 mOnTuneListener = null; 561 setTimeShiftAvailable(false); 562 } 563 setMain()564 public void setMain() { 565 if (PermissionUtils.hasChangeHdmiCecActiveSource(getContext())) { 566 mTvView.setMain(); 567 } 568 } 569 setWatchedHistoryManager(WatchedHistoryManager watchedHistoryManager)570 public void setWatchedHistoryManager(WatchedHistoryManager watchedHistoryManager) { 571 mWatchedHistoryManager = watchedHistoryManager; 572 } 573 574 /** Sets if the TunableTvView is under shrunken. */ setIsUnderShrunken(boolean isUnderShrunken)575 public void setIsUnderShrunken(boolean isUnderShrunken) { 576 mIsUnderShrunken = isUnderShrunken; 577 } 578 getChannelSignalStrength()579 public int getChannelSignalStrength() { 580 return mChannelSignalStrength; 581 } 582 resetChannelSignalStrength()583 public void resetChannelSignalStrength() { 584 mChannelSignalStrength = TvInputConstantCompat.SIGNAL_STRENGTH_NOT_USED; 585 } 586 587 @Override isPlaying()588 public boolean isPlaying() { 589 return mStarted; 590 } 591 592 /** Called when parental control is changed. */ onParentalControlChanged(boolean enabled)593 public void onParentalControlChanged(boolean enabled) { 594 mParentControlEnabled = enabled; 595 if (!enabled) { 596 // Unblock screen immediately if parental control is turned off 597 updateBlockScreenAndMuting(); 598 } 599 } 600 601 /** 602 * Tunes to a channel with the {@code channelId}. 603 * 604 * @param params extra data to send it to TIS and store the data in TIMS. 605 * @return false, if the TV input is not a proper state to tune to a channel. For example, if 606 * the state is disconnected or channelId doesn't exist, it returns false. 607 */ tuneTo(Channel channel, Bundle params, OnTuneListener listener)608 public boolean tuneTo(Channel channel, Bundle params, OnTuneListener listener) { 609 Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TunableTvView.tuneTo"); 610 if (!mStarted) { 611 throw new IllegalStateException("TvView isn't started"); 612 } 613 if (DEBUG) Log.d(TAG, "tuneTo " + channel); 614 TvInputInfo inputInfo = mInputManagerHelper.getTvInputInfo(channel.getInputId()); 615 if (inputInfo == null) { 616 return false; 617 } 618 if (mCurrentChannel != null) { 619 long duration = mChannelViewTimer.reset(); 620 mTracker.sendChannelViewStop(mCurrentChannel, duration); 621 if (mWatchedHistoryManager != null && !mCurrentChannel.isPassthrough()) { 622 mWatchedHistoryManager.logChannelViewStop( 623 mCurrentChannel, System.currentTimeMillis(), duration); 624 } 625 } 626 mOnTuneListener = listener; 627 mCurrentChannel = channel; 628 boolean tunedByRecommendation = 629 params != null 630 && params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) 631 != null; 632 boolean needSurfaceSizeUpdate = false; 633 if (!inputInfo.equals(mInputInfo)) { 634 mTagetInputId = inputInfo.getId(); 635 mInputInfo = inputInfo; 636 mCanReceiveInputEvent = 637 getContext() 638 .getPackageManager() 639 .checkPermission( 640 PERMISSION_RECEIVE_INPUT_EVENT, 641 mInputInfo.getServiceInfo().packageName) 642 == PackageManager.PERMISSION_GRANTED; 643 if (DEBUG) { 644 Log.d( 645 TAG, 646 "Input \'" 647 + mInputInfo.getId() 648 + "\' can receive input event: " 649 + mCanReceiveInputEvent); 650 } 651 needSurfaceSizeUpdate = true; 652 } 653 mTracker.sendChannelViewStart(mCurrentChannel, tunedByRecommendation); 654 mChannelViewTimer.start(); 655 mVideoWidth = 0; 656 mVideoHeight = 0; 657 mVideoFormat = StreamInfo.VIDEO_DEFINITION_LEVEL_UNKNOWN; 658 mVideoFrameRate = 0f; 659 mVideoDisplayAspectRatio = 0f; 660 mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN; 661 mHasClosedCaption = false; 662 mBlockedContentRating = null; 663 mTimeShiftCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME; 664 // To reduce the IPCs, unregister the callback here and register it when necessary. 665 mTvView.setTimeShiftPositionCallback(null); 666 setTimeShiftAvailable(false); 667 mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING; 668 if (mTvViewSession != null) { 669 mTvViewSession.tune(channel, params, listener); 670 } else { 671 mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params); 672 } 673 if (needSurfaceSizeUpdate && mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) { 674 // When the input is changed, TvView recreates its SurfaceView internally. 675 // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView. 676 SurfaceView surfaceView = getSurfaceView(); 677 if (surfaceView != null) { 678 surfaceView.getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight); 679 } else { 680 Log.w(TAG, "Failed to set fixed size for surface view: Null surface view"); 681 } 682 } 683 updateBlockScreenAndMuting(); 684 if (mOnTuneListener != null) { 685 mOnTuneListener.onStreamInfoChanged(this, true); 686 } 687 return true; 688 } 689 690 @Override 691 @Nullable getCurrentChannel()692 public Channel getCurrentChannel() { 693 return mCurrentChannel; 694 } 695 696 /** 697 * Sets the current channel. Call this method only when setting the current channel without 698 * actually tuning to it. 699 * 700 * @param currentChannel The new current channel to set to. 701 */ setCurrentChannel(Channel currentChannel)702 public void setCurrentChannel(Channel currentChannel) { 703 mCurrentChannel = currentChannel; 704 } 705 706 @Override setStreamVolume(float volume)707 public void setStreamVolume(float volume) { 708 if (!mStarted) { 709 throw new IllegalStateException("TvView isn't started"); 710 } 711 if (DEBUG) Log.d(TAG, "setStreamVolume " + volume); 712 mVolume = volume; 713 if (!mIsMuted) { 714 mTvView.setStreamVolume(volume); 715 } 716 } 717 718 /** 719 * Sets fixed size for the internal {@link android.view.Surface} of {@link 720 * android.media.tv.TvView}. If either {@code width} or {@code height} is non positive, the 721 * {@link android.view.Surface}'s size will be matched to the layout. 722 * 723 * <p>Note: Once {@link android.view.SurfaceHolder#setFixedSize} is called, {@link 724 * android.view.SurfaceView} and its underlying window can be misaligned, when the size of 725 * {@link android.view.SurfaceView} is changed without changing either left position or top 726 * position. For detail, please refer the codes of android.view.SurfaceView.updateWindow(). 727 */ setFixedSurfaceSize(int width, int height)728 public void setFixedSurfaceSize(int width, int height) { 729 mFixedSurfaceWidth = width; 730 mFixedSurfaceHeight = height; 731 if (mFixedSurfaceWidth > 0 && mFixedSurfaceHeight > 0) { 732 // When the input is changed, TvView recreates its SurfaceView internally. 733 // So we need to call SurfaceHolder.setFixedSize for the new SurfaceView. 734 SurfaceView surfaceView = (SurfaceView) mTvView.getChildAt(0); 735 surfaceView.getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight); 736 } else { 737 SurfaceView surfaceView = (SurfaceView) mTvView.getChildAt(0); 738 surfaceView.getHolder().setSizeFromLayout(); 739 } 740 } 741 742 @Override dispatchKeyEvent(KeyEvent event)743 public boolean dispatchKeyEvent(KeyEvent event) { 744 return mCanReceiveInputEvent && mTvView.dispatchKeyEvent(event); 745 } 746 747 @Override dispatchTouchEvent(MotionEvent event)748 public boolean dispatchTouchEvent(MotionEvent event) { 749 return mCanReceiveInputEvent && mTvView.dispatchTouchEvent(event); 750 } 751 752 @Override dispatchTrackballEvent(MotionEvent event)753 public boolean dispatchTrackballEvent(MotionEvent event) { 754 return mCanReceiveInputEvent && mTvView.dispatchTrackballEvent(event); 755 } 756 757 @Override dispatchGenericMotionEvent(MotionEvent event)758 public boolean dispatchGenericMotionEvent(MotionEvent event) { 759 return mCanReceiveInputEvent && mTvView.dispatchGenericMotionEvent(event); 760 } 761 762 public interface OnTuneListener { onTuneFailed(Channel channel)763 void onTuneFailed(Channel channel); 764 onUnexpectedStop(Channel channel)765 void onUnexpectedStop(Channel channel); 766 onStreamInfoChanged(StreamInfo info, boolean allowAutoSelectionOfTrack)767 void onStreamInfoChanged(StreamInfo info, boolean allowAutoSelectionOfTrack); 768 onChannelRetuned(Uri channel)769 void onChannelRetuned(Uri channel); 770 onContentBlocked()771 void onContentBlocked(); 772 onContentAllowed()773 void onContentAllowed(); 774 onChannelSignalStrength()775 void onChannelSignalStrength(); 776 } 777 unblockContent(TvContentRating rating)778 public void unblockContent(TvContentRating rating) { 779 mTvView.unblockContent(rating); 780 } 781 782 @Override getVideoWidth()783 public int getVideoWidth() { 784 return mVideoWidth; 785 } 786 787 @Override getVideoHeight()788 public int getVideoHeight() { 789 return mVideoHeight; 790 } 791 792 @Override getVideoDefinitionLevel()793 public int getVideoDefinitionLevel() { 794 return mVideoFormat; 795 } 796 797 @Override getVideoFrameRate()798 public float getVideoFrameRate() { 799 return mVideoFrameRate; 800 } 801 802 /** Returns displayed aspect ratio (video width / video height * pixel ratio). */ 803 @Override getVideoDisplayAspectRatio()804 public float getVideoDisplayAspectRatio() { 805 return mVideoDisplayAspectRatio; 806 } 807 808 @Override getAudioChannelCount()809 public int getAudioChannelCount() { 810 return mAudioChannelCount; 811 } 812 813 @Override hasClosedCaption()814 public boolean hasClosedCaption() { 815 return mHasClosedCaption; 816 } 817 818 @Override isVideoAvailable()819 public boolean isVideoAvailable() { 820 return mVideoUnavailableReason == VIDEO_UNAVAILABLE_REASON_NONE; 821 } 822 823 @Override isVideoOrAudioAvailable()824 public boolean isVideoOrAudioAvailable() { 825 return mVideoUnavailableReason == VIDEO_UNAVAILABLE_REASON_NONE 826 || mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY; 827 } 828 829 @Override getVideoUnavailableReason()830 public int getVideoUnavailableReason() { 831 return mVideoUnavailableReason; 832 } 833 834 /** Returns the {@link android.view.SurfaceView} of the {@link android.media.tv.TvView}. */ getSurfaceView()835 private SurfaceView getSurfaceView() { 836 return (SurfaceView) mTvView.getChildAt(0); 837 } 838 setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener)839 public void setOnUnhandledInputEventListener(OnUnhandledInputEventListener listener) { 840 mTvView.setOnUnhandledInputEventListener(listener); 841 } 842 setClosedCaptionEnabled(boolean enabled)843 public void setClosedCaptionEnabled(boolean enabled) { 844 mTvView.setCaptionEnabled(enabled); 845 } 846 847 @VisibleForTesting setOnTuneListener(OnTuneListener listener)848 public void setOnTuneListener(OnTuneListener listener) { 849 mOnTuneListener = listener; 850 } 851 getTracks(int type)852 public List<TvTrackInfo> getTracks(int type) { 853 return mTvView.getTracks(type); 854 } 855 getSelectedTrack(int type)856 public String getSelectedTrack(int type) { 857 return mTvView.getSelectedTrack(type); 858 } 859 selectTrack(int type, String trackId)860 public void selectTrack(int type, String trackId) { 861 mTvView.selectTrack(type, trackId); 862 } 863 864 /** 865 * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying {@link TvView}, 866 * which is the actual view to play live TV videos. 867 */ getTvViewLayoutParams()868 public MarginLayoutParams getTvViewLayoutParams() { 869 return (MarginLayoutParams) mTvView.getLayoutParams(); 870 } 871 872 /** 873 * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying {@link TvView}, 874 * which is the actual view to play live TV videos. 875 */ setTvViewLayoutParams(MarginLayoutParams layoutParams)876 public void setTvViewLayoutParams(MarginLayoutParams layoutParams) { 877 mTvView.setLayoutParams(layoutParams); 878 } 879 880 /** 881 * Gets the underlying {@link AppLayerTvView}, which is the actual view to play live TV videos. 882 */ getTvView()883 public TvView getTvView() { 884 return mTvView; 885 } 886 887 /** 888 * Returns if the screen is blocked, either by {@link #blockOrUnblockScreen(boolean)} or because 889 * the content is blocked. 890 */ isBlocked()891 public boolean isBlocked() { 892 return isScreenBlocked() || isContentBlocked(); 893 } 894 895 /** Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}. */ isScreenBlocked()896 public boolean isScreenBlocked() { 897 return mScreenBlocked; 898 } 899 900 /** Returns {@code true} if the content is blocked, otherwise {@code false}. */ isContentBlocked()901 public boolean isContentBlocked() { 902 return mBlockedContentRating != null; 903 } 904 setOnScreenBlockedListener(OnScreenBlockingChangedListener listener)905 public void setOnScreenBlockedListener(OnScreenBlockingChangedListener listener) { 906 mOnScreenBlockedListener = listener; 907 } 908 909 /** Returns currently blocked content rating. {@code null} if it's not blocked. */ 910 @Override getBlockedContentRating()911 public TvContentRating getBlockedContentRating() { 912 return mBlockedContentRating; 913 } 914 915 /** 916 * Blocks/unblocks current TV screen and mutes. There would be black screen with lock icon in 917 * order to show that screen block is intended and not an error. 918 * 919 * @param blockOrUnblock {@code true} to block the screen, or {@code false} to unblock. 920 */ blockOrUnblockScreen(boolean blockOrUnblock)921 public void blockOrUnblockScreen(boolean blockOrUnblock) { 922 if (mScreenBlocked == blockOrUnblock) { 923 return; 924 } 925 mScreenBlocked = blockOrUnblock; 926 if (closePipIfNeeded()) { 927 return; 928 } 929 updateBlockScreenAndMuting(); 930 if (mOnScreenBlockedListener != null) { 931 mOnScreenBlockedListener.onScreenBlockingChanged(blockOrUnblock); 932 } 933 } 934 935 @Override onVisibilityChanged(@onNull View changedView, int visibility)936 protected void onVisibilityChanged(@NonNull View changedView, int visibility) { 937 super.onVisibilityChanged(changedView, visibility); 938 if (mTvView != null) { 939 mTvView.setVisibility(visibility); 940 } 941 } 942 943 /** 944 * Set the type of block screen. If {@code type} is set to {@code BLOCK_SCREEN_TYPE_NO_UI}, the 945 * block screen will not show any description such as a lock icon and a text for the blocked 946 * reason, if {@code type} is set to {@code BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW}, the block 947 * screen will show the description for shrunken tv view (Small icon and short text), and if 948 * {@code type} is set to {@code BLOCK_SCREEN_TYPE_NORMAL}, the block screen will show the 949 * description for normal tv view (Big icon and long text). 950 * 951 * @param type The type of block screen to set. 952 */ setBlockScreenType(@lockScreenType int type)953 public void setBlockScreenType(@BlockScreenType int type) { 954 if (mBlockScreenType != type) { 955 mBlockScreenType = type; 956 updateBlockScreen(true); 957 } 958 } 959 updateBlockScreen(boolean animation)960 private void updateBlockScreen(boolean animation) { 961 mBlockScreenView.endAnimations(); 962 int blockReason = 963 (mScreenBlocked || mBlockedContentRating != null) && mParentControlEnabled 964 ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED 965 : mVideoUnavailableReason; 966 if (blockReason != VIDEO_UNAVAILABLE_REASON_NONE) { 967 mBufferingSpinnerView.setVisibility( 968 blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING 969 || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING 970 ? VISIBLE 971 : GONE); 972 if (!animation) { 973 adjustBlockScreenSpacingAndText(); 974 } 975 if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) { 976 return; 977 } 978 mBlockScreenView.setVisibility(VISIBLE); 979 mBlockScreenView.setBackgroundImage(null); 980 if (blockReason == VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED) { 981 mBlockScreenView.setIconVisibility(true); 982 if (!mCanModifyParentalControls) { 983 mBlockScreenView.setIconImage(R.drawable.ic_message_lock_no_permission); 984 mBlockScreenView.setIconScaleType(ImageView.ScaleType.CENTER); 985 } else { 986 mBlockScreenView.setIconImage(R.drawable.ic_message_lock); 987 mBlockScreenView.setIconScaleType(ImageView.ScaleType.FIT_CENTER); 988 } 989 } else { 990 if (mInternetCheckTask != null) { 991 mInternetCheckTask.cancel(true); 992 mInternetCheckTask = null; 993 } 994 mBlockScreenView.setIconVisibility(false); 995 if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) { 996 showImageForTuningIfNeeded(); 997 } else if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN 998 && mCurrentChannel != null 999 && !mCurrentChannel.isPhysicalTunerChannel()) { 1000 mInternetCheckTask = new InternetCheckTask(); 1001 mInternetCheckTask.execute(); 1002 } 1003 } 1004 mBlockScreenView.onBlockStatusChanged(mBlockScreenType, animation); 1005 } else { 1006 mBufferingSpinnerView.setVisibility(GONE); 1007 if (mBlockScreenView.getVisibility() == VISIBLE) { 1008 mBlockScreenView.fadeOut(); 1009 } 1010 } 1011 } 1012 adjustBlockScreenSpacingAndText()1013 private void adjustBlockScreenSpacingAndText() { 1014 mBlockScreenView.setSpacing(mBlockScreenType); 1015 String text = getBlockScreenText(); 1016 if (text != null) { 1017 mBlockScreenView.setInfoText(text); 1018 } 1019 mBlockScreenView.setInfoTextClickable(mScreenBlocked && mParentControlEnabled); 1020 } 1021 1022 /** 1023 * Returns the block screen text corresponding to the current status. Note that returning {@code 1024 * null} value means that the current text should not be changed. 1025 */ getBlockScreenText()1026 private String getBlockScreenText() { 1027 // TODO: add a test for this method 1028 Resources res = getResources(); 1029 boolean isA11y = mAccessibilityManager.isEnabled(); 1030 1031 if (mScreenBlocked && mParentControlEnabled) { 1032 switch (mBlockScreenType) { 1033 case BLOCK_SCREEN_TYPE_NO_UI: 1034 case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: 1035 return ""; 1036 case BLOCK_SCREEN_TYPE_NORMAL: 1037 if (mCanModifyParentalControls) { 1038 return res.getString( 1039 isA11y 1040 ? R.string.tvview_channel_locked_talkback 1041 : R.string.tvview_channel_locked); 1042 } else { 1043 return res.getString(R.string.tvview_channel_locked_no_permission); 1044 } 1045 } 1046 } else if (mBlockedContentRating != null && mParentControlEnabled) { 1047 String name = mContentRatingsManager.getDisplayNameForRating(mBlockedContentRating); 1048 switch (mBlockScreenType) { 1049 case BLOCK_SCREEN_TYPE_NO_UI: 1050 return ""; 1051 case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW: 1052 if (TextUtils.isEmpty(name)) { 1053 return res.getString(R.string.shrunken_tvview_content_locked); 1054 } else if (name.equals(res.getString(R.string.unrated_rating_name))) { 1055 return res.getString(R.string.shrunken_tvview_content_locked_unrated); 1056 } else { 1057 return res.getString(R.string.shrunken_tvview_content_locked_format, name); 1058 } 1059 case BLOCK_SCREEN_TYPE_NORMAL: 1060 if (TextUtils.isEmpty(name)) { 1061 if (mCanModifyParentalControls) { 1062 return res.getString( 1063 isA11y 1064 ? R.string.tvview_content_locked_talkback 1065 : R.string.tvview_content_locked); 1066 } else { 1067 return res.getString(R.string.tvview_content_locked_no_permission); 1068 } 1069 } else { 1070 if (mCanModifyParentalControls) { 1071 return name.equals(res.getString(R.string.unrated_rating_name)) 1072 ? res.getString( 1073 isA11y 1074 ? R.string 1075 .tvview_content_locked_unrated_talkback 1076 : R.string.tvview_content_locked_unrated) 1077 : res.getString( 1078 isA11y 1079 ? R.string.tvview_content_locked_format_talkback 1080 : R.string.tvview_content_locked_format, 1081 name); 1082 } else { 1083 return name.equals(res.getString(R.string.unrated_rating_name)) 1084 ? res.getString( 1085 R.string.tvview_content_locked_unrated_no_permission) 1086 : res.getString( 1087 R.string.tvview_content_locked_format_no_permission, 1088 name); 1089 } 1090 } 1091 } 1092 } else if (mVideoUnavailableReason != VIDEO_UNAVAILABLE_REASON_NONE) { 1093 switch (mVideoUnavailableReason) { 1094 case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY: 1095 return res.getString(R.string.tvview_msg_audio_only); 1096 case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: 1097 return res.getString(R.string.tvview_msg_weak_signal); 1098 case CommonConstants.VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED: 1099 return res.getString(R.string.msg_channel_unavailable_not_connected); 1100 case VIDEO_UNAVAILABLE_REASON_NO_RESOURCE: 1101 return getTuneConflictMessage(); 1102 default: 1103 return ""; 1104 } 1105 } 1106 return null; 1107 } 1108 closePipIfNeeded()1109 private boolean closePipIfNeeded() { 1110 if (TvFeatures.PICTURE_IN_PICTURE.isEnabled(getContext()) 1111 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N 1112 && ((Activity) getContext()).isInPictureInPictureMode() 1113 && (mScreenBlocked 1114 || mBlockedContentRating != null 1115 || mVideoUnavailableReason 1116 == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN 1117 || mVideoUnavailableReason 1118 == CommonConstants.VIDEO_UNAVAILABLE_REASON_NOT_CONNECTED)) { 1119 ((Activity) getContext()).finish(); 1120 return true; 1121 } 1122 return false; 1123 } 1124 updateBlockScreenAndMuting()1125 private void updateBlockScreenAndMuting() { 1126 updateBlockScreen(false); 1127 updateMuteStatus(); 1128 } 1129 shouldShowImageForTuning()1130 private boolean shouldShowImageForTuning() { 1131 if (mVideoUnavailableReason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING 1132 || mScreenBlocked 1133 || mBlockedContentRating != null 1134 || mCurrentChannel == null 1135 || mIsUnderShrunken 1136 || getWidth() == 0 1137 || getWidth() == 0 1138 || !isBundledInput()) { 1139 return false; 1140 } 1141 Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId()); 1142 if (currentProgram == null) { 1143 return false; 1144 } 1145 TvContentRating rating = 1146 mParentalControlSettings.getBlockedRating(currentProgram.getContentRatings()); 1147 return !(mParentControlEnabled && rating != null); 1148 } 1149 showImageForTuningIfNeeded()1150 private void showImageForTuningIfNeeded() { 1151 if (shouldShowImageForTuning()) { 1152 if (mCurrentChannel == null) { 1153 return; 1154 } 1155 Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId()); 1156 if (currentProgram != null) { 1157 currentProgram.loadPosterArt( 1158 getContext(), 1159 getWidth(), 1160 getHeight(), 1161 createProgramPosterArtCallback(mCurrentChannel.getId())); 1162 } 1163 } 1164 } 1165 getTuneConflictMessage()1166 private String getTuneConflictMessage() { 1167 if (mTagetInputId != null) { 1168 TvInputInfo input = mInputManager.getTvInputInfo(mTagetInputId); 1169 Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(mTagetInputId); 1170 if (timeMs != null) { 1171 return getResources() 1172 .getQuantityString( 1173 R.plurals.tvview_msg_input_no_resource, 1174 input.getTunerCount(), 1175 DateUtils.formatDateTime( 1176 getContext(), timeMs, DateUtils.FORMAT_SHOW_TIME)); 1177 } 1178 } 1179 return null; 1180 } 1181 updateMuteStatus()1182 private void updateMuteStatus() { 1183 // Workaround: BaseTunerTvInputService uses AC3 pass-through implementation, which disables 1184 // audio tracks to enforce the mute request. We don't want to send mute request if we are 1185 // not going to block the screen to prevent the video jankiness resulted by disabling audio 1186 // track before the playback is started. In other way, we should send unmute request before 1187 // the playback is started, because TunerTvInput will remember the muted state and mute 1188 // itself right way when the playback is going to be started, which results the initial 1189 // jankiness, too. 1190 boolean isBundledInput = isBundledInput(); 1191 if ((isBundledInput || isVideoOrAudioAvailable()) 1192 && !mScreenBlocked 1193 && mBlockedContentRating == null) { 1194 if (mIsMuted) { 1195 mIsMuted = false; 1196 mTvView.setStreamVolume(mVolume); 1197 } 1198 } else { 1199 if (!mIsMuted) { 1200 if ((mInputInfo == null || isBundledInput) 1201 && !mScreenBlocked 1202 && mBlockedContentRating == null) { 1203 return; 1204 } 1205 mIsMuted = true; 1206 mTvView.setStreamVolume(0); 1207 } 1208 } 1209 } 1210 isBundledInput()1211 private boolean isBundledInput() { 1212 return mInputInfo != null 1213 && mInputInfo.getType() == TvInputInfo.TYPE_TUNER 1214 && CommonUtils.isBundledInput(mInputInfo.getId()); 1215 } 1216 1217 /** Returns true if this view is faded out. */ isFadedOut()1218 public boolean isFadedOut() { 1219 return mFadeState == FADED_OUT; 1220 } 1221 1222 /** Fade out this TunableTvView. Fade out by increasing the dimming. */ fadeOut( int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade)1223 public void fadeOut( 1224 int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade) { 1225 mDimScreenView.setAlpha(0f); 1226 mDimScreenView.setVisibility(View.VISIBLE); 1227 mDimScreenView 1228 .animate() 1229 .alpha(1f) 1230 .setDuration(durationMillis) 1231 .setInterpolator(interpolator) 1232 .withStartAction( 1233 () -> { 1234 mFadeState = FADING_OUT; 1235 mActionAfterFade = actionAfterFade; 1236 }) 1237 .withEndAction(() -> mFadeState = FADED_OUT); 1238 } 1239 1240 /** Fade in this TunableTvView. Fade in by decreasing the dimming. */ fadeIn( int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade)1241 public void fadeIn( 1242 int durationMillis, TimeInterpolator interpolator, final Runnable actionAfterFade) { 1243 mDimScreenView.setAlpha(1f); 1244 mDimScreenView.setVisibility(View.VISIBLE); 1245 mDimScreenView 1246 .animate() 1247 .alpha(0f) 1248 .setDuration(durationMillis) 1249 .setInterpolator(interpolator) 1250 .withStartAction( 1251 () -> { 1252 mFadeState = FADING_IN; 1253 mActionAfterFade = actionAfterFade; 1254 }) 1255 .withEndAction( 1256 () -> { 1257 mFadeState = FADED_IN; 1258 mDimScreenView.setVisibility(View.GONE); 1259 }); 1260 } 1261 1262 /** Remove the fade effect. */ removeFadeEffect()1263 public void removeFadeEffect() { 1264 mDimScreenView.animate().cancel(); 1265 mDimScreenView.setVisibility(View.GONE); 1266 mFadeState = FADED_IN; 1267 } 1268 1269 /** 1270 * Sets the TimeShiftListener 1271 * 1272 * @param listener The instance of {@link TimeShiftListener}. 1273 */ 1274 @Override setTimeShiftListener(TimeShiftListener listener)1275 public void setTimeShiftListener(TimeShiftListener listener) { 1276 mTimeShiftListener = listener; 1277 } 1278 setBlockedInfoOnClickListener(@ullable OnClickListener onClickListener)1279 public void setBlockedInfoOnClickListener(@Nullable OnClickListener onClickListener) { 1280 mBlockScreenView.setInfoTextOnClickListener(onClickListener); 1281 } 1282 setTimeShiftAvailable(boolean isTimeShiftAvailable)1283 private void setTimeShiftAvailable(boolean isTimeShiftAvailable) { 1284 if (mTimeShiftAvailable == isTimeShiftAvailable) { 1285 return; 1286 } 1287 mTimeShiftAvailable = isTimeShiftAvailable; 1288 if (isTimeShiftAvailable) { 1289 mTvView.setTimeShiftPositionCallback( 1290 new TvView.TimeShiftPositionCallback() { 1291 @Override 1292 public void onTimeShiftStartPositionChanged(String inputId, long timeMs) { 1293 if (mTimeShiftListener != null 1294 && mCurrentChannel != null 1295 && mCurrentChannel.getInputId().equals(inputId)) { 1296 mTimeShiftListener.onRecordStartTimeChanged(timeMs); 1297 } 1298 } 1299 1300 @Override 1301 public void onTimeShiftCurrentPositionChanged(String inputId, long timeMs) { 1302 mTimeShiftCurrentPositionMs = timeMs; 1303 } 1304 }); 1305 } else { 1306 mTvView.setTimeShiftPositionCallback(null); 1307 } 1308 if (mTimeShiftListener != null) { 1309 mTimeShiftListener.onAvailabilityChanged(); 1310 } 1311 } 1312 1313 /** Returns if the time shift is available for the current channel. */ 1314 @Override isTimeShiftAvailable()1315 public boolean isTimeShiftAvailable() { 1316 return mTimeShiftAvailable; 1317 } 1318 1319 /** Plays the media, if the current input supports time-shifting. */ 1320 @Override timeShiftPlay()1321 public void timeShiftPlay() { 1322 if (!isTimeShiftAvailable()) { 1323 throw new IllegalStateException("Time-shift is not supported for the current channel"); 1324 } 1325 if (mTimeShiftState == TIME_SHIFT_STATE_PLAY) { 1326 return; 1327 } 1328 mTvView.timeShiftResume(); 1329 } 1330 1331 /** Pauses the media, if the current input supports time-shifting. */ 1332 @Override timeShiftPause()1333 public void timeShiftPause() { 1334 if (!isTimeShiftAvailable()) { 1335 throw new IllegalStateException("Time-shift is not supported for the current channel"); 1336 } 1337 if (mTimeShiftState == TIME_SHIFT_STATE_PAUSE) { 1338 return; 1339 } 1340 mTvView.timeShiftPause(); 1341 } 1342 1343 /** 1344 * Rewinds the media with the given speed, if the current input supports time-shifting. 1345 * 1346 * @param speed The speed to rewind the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x. 1347 */ 1348 @Override timeShiftRewind(int speed)1349 public void timeShiftRewind(int speed) { 1350 if (!isTimeShiftAvailable()) { 1351 throw new IllegalStateException("Time-shift is not supported for the current channel"); 1352 } else { 1353 if (speed <= 0) { 1354 throw new IllegalArgumentException("The speed should be a positive integer."); 1355 } 1356 mTimeShiftState = TIME_SHIFT_STATE_REWIND; 1357 PlaybackParams params = new PlaybackParams(); 1358 params.setSpeed(speed * -1); 1359 mTvView.timeShiftSetPlaybackParams(params); 1360 } 1361 } 1362 1363 /** 1364 * Fast-forwards the media with the given speed, if the current input supports time-shifting. 1365 * 1366 * @param speed The speed to forward the media. e.g. 2 for 2x, 3 for 3x and 4 for 4x. 1367 */ 1368 @Override timeShiftFastForward(int speed)1369 public void timeShiftFastForward(int speed) { 1370 if (!isTimeShiftAvailable()) { 1371 throw new IllegalStateException("Time-shift is not supported for the current channel"); 1372 } else { 1373 if (speed <= 0) { 1374 throw new IllegalArgumentException("The speed should be a positive integer."); 1375 } 1376 mTimeShiftState = TIME_SHIFT_STATE_FAST_FORWARD; 1377 PlaybackParams params = new PlaybackParams(); 1378 params.setSpeed(speed); 1379 mTvView.timeShiftSetPlaybackParams(params); 1380 } 1381 } 1382 1383 /** 1384 * Seek to the given time position. 1385 * 1386 * @param timeMs The time in milliseconds to seek to. 1387 */ 1388 @Override timeShiftSeekTo(long timeMs)1389 public void timeShiftSeekTo(long timeMs) { 1390 if (!isTimeShiftAvailable()) { 1391 throw new IllegalStateException("Time-shift is not supported for the current channel"); 1392 } 1393 mTvView.timeShiftSeekTo(timeMs); 1394 } 1395 1396 /** Returns the current playback position in milliseconds. */ 1397 @Override timeShiftGetCurrentPositionMs()1398 public long timeShiftGetCurrentPositionMs() { 1399 if (!isTimeShiftAvailable()) { 1400 throw new IllegalStateException("Time-shift is not supported for the current channel"); 1401 } 1402 if (DEBUG) { 1403 Log.d( 1404 TAG, 1405 "timeShiftGetCurrentPositionMs: current position =" 1406 + Utils.toTimeString(mTimeShiftCurrentPositionMs)); 1407 } 1408 return mTimeShiftCurrentPositionMs; 1409 } 1410 createProgramPosterArtCallback( final long channelId)1411 private ImageLoader.ImageLoaderCallback<BlockScreenView> createProgramPosterArtCallback( 1412 final long channelId) { 1413 return new ImageLoader.ImageLoaderCallback<BlockScreenView>(mBlockScreenView) { 1414 @Override 1415 public void onBitmapLoaded(BlockScreenView view, @Nullable Bitmap posterArt) { 1416 if (posterArt == null 1417 || getCurrentChannel() == null 1418 || channelId != getCurrentChannel().getId() 1419 || !shouldShowImageForTuning()) { 1420 return; 1421 } 1422 Drawable drawablePosterArt = new BitmapDrawable(view.getResources(), posterArt); 1423 drawablePosterArt 1424 .mutate() 1425 .setColorFilter(mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER); 1426 view.setBackgroundImage(drawablePosterArt); 1427 } 1428 }; 1429 } 1430 1431 /** A listener which receives the notification when the screen is blocked/unblocked. */ 1432 public abstract static class OnScreenBlockingChangedListener { 1433 /** Called when the screen is blocked/unblocked. */ 1434 public abstract void onScreenBlockingChanged(boolean blocked); 1435 } 1436 1437 private class InternetCheckTask extends AsyncTask<Void, Void, Boolean> { 1438 @Override 1439 protected Boolean doInBackground(Void... params) { 1440 return NetworkUtils.isNetworkAvailable(mConnectivityManager); 1441 } 1442 1443 @Override 1444 protected void onPostExecute(Boolean networkAvailable) { 1445 mInternetCheckTask = null; 1446 if (!networkAvailable 1447 && isAttachedToWindow() 1448 && !mScreenBlocked 1449 && mBlockedContentRating == null 1450 && mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN) { 1451 mBlockScreenView.setIconVisibility(true); 1452 mBlockScreenView.setIconImage(R.drawable.ic_sad_cloud); 1453 mBlockScreenView.setInfoText(R.string.tvview_msg_no_internet_connection); 1454 } 1455 } 1456 } 1457 } 1458