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