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