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.AnimatorInflater; 21 import android.animation.AnimatorListenerAdapter; 22 import android.animation.AnimatorSet; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.graphics.Bitmap; 27 import android.media.tv.TvContentRating; 28 import android.media.tv.TvInputInfo; 29 import android.support.annotation.Nullable; 30 import android.text.Spannable; 31 import android.text.SpannableString; 32 import android.text.TextUtils; 33 import android.text.format.DateUtils; 34 import android.text.style.TextAppearanceSpan; 35 import android.util.AttributeSet; 36 import android.util.Log; 37 import android.util.TypedValue; 38 import android.view.View; 39 import android.view.ViewGroup; 40 import android.view.accessibility.AccessibilityManager; 41 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; 42 import android.view.animation.AnimationUtils; 43 import android.view.animation.Interpolator; 44 import android.widget.FrameLayout; 45 import android.widget.ImageView; 46 import android.widget.ProgressBar; 47 import android.widget.RelativeLayout; 48 import android.widget.TextView; 49 import com.android.tv.MainActivity; 50 import com.android.tv.R; 51 import com.android.tv.TvSingletons; 52 import com.android.tv.common.SoftPreconditions; 53 import com.android.tv.common.feature.CommonFeatures; 54 import com.android.tv.data.Program; 55 import com.android.tv.data.StreamInfo; 56 import com.android.tv.data.api.Channel; 57 import com.android.tv.dvr.DvrManager; 58 import com.android.tv.dvr.data.ScheduledRecording; 59 import com.android.tv.parental.ContentRatingsManager; 60 import com.android.tv.ui.TvTransitionManager.TransitionLayout; 61 import com.android.tv.ui.hideable.AutoHideScheduler; 62 import com.android.tv.util.Utils; 63 import com.android.tv.util.images.ImageCache; 64 import com.android.tv.util.images.ImageLoader; 65 import com.android.tv.util.images.ImageLoader.ImageLoaderCallback; 66 import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask; 67 68 /** A view to render channel banner. */ 69 public class ChannelBannerView extends FrameLayout 70 implements TransitionLayout, AccessibilityStateChangeListener { 71 private static final String TAG = "ChannelBannerView"; 72 private static final boolean DEBUG = false; 73 74 /** Show all information at the channel banner. */ 75 public static final int LOCK_NONE = 0; 76 77 /** 78 * Lock program details at the channel banner. This is used when a content is locked so we don't 79 * want to show program details including program description text and poster art. 80 */ 81 public static final int LOCK_PROGRAM_DETAIL = 1; 82 83 /** 84 * Lock channel information at the channel banner. This is used when a channel is locked so we 85 * only want to show input information. 86 */ 87 public static final int LOCK_CHANNEL_INFO = 2; 88 89 private static final int DISPLAYED_CONTENT_RATINGS_COUNT = 3; 90 91 private static final String EMPTY_STRING = ""; 92 93 private Program mNoProgram; 94 private Program mLockedChannelProgram; 95 private static String sClosedCaptionMark; 96 97 private final MainActivity mMainActivity; 98 private final Resources mResources; 99 private View mChannelView; 100 101 private TextView mChannelNumberTextView; 102 private ImageView mChannelLogoImageView; 103 private TextView mProgramTextView; 104 private ImageView mTvInputLogoImageView; 105 private TextView mChannelNameTextView; 106 private TextView mProgramTimeTextView; 107 private ProgressBar mRemainingTimeView; 108 private TextView mRecordingIndicatorView; 109 private TextView mClosedCaptionTextView; 110 private TextView mAspectRatioTextView; 111 private TextView mResolutionTextView; 112 private TextView mAudioChannelTextView; 113 private TextView[] mContentRatingsTextViews = new TextView[DISPLAYED_CONTENT_RATINGS_COUNT]; 114 private TextView mProgramDescriptionTextView; 115 private String mProgramDescriptionText; 116 private View mAnchorView; 117 private Channel mCurrentChannel; 118 private boolean mCurrentChannelLogoExists; 119 private Program mLastUpdatedProgram; 120 private final AutoHideScheduler mAutoHideScheduler; 121 private final DvrManager mDvrManager; 122 private ContentRatingsManager mContentRatingsManager; 123 private TvContentRating mBlockingContentRating; 124 125 private int mLockType; 126 private boolean mUpdateOnTune; 127 128 private Animator mResizeAnimator; 129 private int mCurrentHeight; 130 private boolean mProgramInfoUpdatePendingByResizing; 131 132 private final Animator mProgramDescriptionFadeInAnimator; 133 private final Animator mProgramDescriptionFadeOutAnimator; 134 135 private final long mShowDurationMillis; 136 private final int mChannelLogoImageViewWidth; 137 private final int mChannelLogoImageViewHeight; 138 private final int mChannelLogoImageViewMarginStart; 139 private final int mProgramDescriptionTextViewWidth; 140 private final int mChannelBannerTextColor; 141 private final int mChannelBannerDimTextColor; 142 private final int mResizeAnimDuration; 143 private final int mRecordingIconPadding; 144 private final Interpolator mResizeInterpolator; 145 146 private final AnimatorListenerAdapter mResizeAnimatorListener = 147 new AnimatorListenerAdapter() { 148 @Override 149 public void onAnimationStart(Animator animator) { 150 mProgramInfoUpdatePendingByResizing = false; 151 } 152 153 @Override 154 public void onAnimationEnd(Animator animator) { 155 mProgramDescriptionTextView.setAlpha(1f); 156 mResizeAnimator = null; 157 if (mProgramInfoUpdatePendingByResizing) { 158 mProgramInfoUpdatePendingByResizing = false; 159 updateProgramInfo(mLastUpdatedProgram); 160 } 161 } 162 }; 163 ChannelBannerView(Context context)164 public ChannelBannerView(Context context) { 165 this(context, null); 166 } 167 ChannelBannerView(Context context, AttributeSet attrs)168 public ChannelBannerView(Context context, AttributeSet attrs) { 169 this(context, attrs, 0); 170 } 171 ChannelBannerView(Context context, AttributeSet attrs, int defStyle)172 public ChannelBannerView(Context context, AttributeSet attrs, int defStyle) { 173 super(context, attrs, defStyle); 174 mResources = getResources(); 175 mMainActivity = (MainActivity) context; 176 177 mShowDurationMillis = mResources.getInteger(R.integer.channel_banner_show_duration); 178 mChannelLogoImageViewWidth = 179 mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_width); 180 mChannelLogoImageViewHeight = 181 mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_height); 182 mChannelLogoImageViewMarginStart = 183 mResources.getDimensionPixelSize(R.dimen.channel_banner_channel_logo_margin_start); 184 mProgramDescriptionTextViewWidth = 185 mResources.getDimensionPixelSize(R.dimen.channel_banner_program_description_width); 186 mChannelBannerTextColor = mResources.getColor(R.color.channel_banner_text_color, null); 187 mChannelBannerDimTextColor = 188 mResources.getColor(R.color.channel_banner_dim_text_color, null); 189 mResizeAnimDuration = mResources.getInteger(R.integer.channel_banner_fast_anim_duration); 190 mRecordingIconPadding = 191 mResources.getDimensionPixelOffset(R.dimen.channel_banner_recording_icon_padding); 192 193 mResizeInterpolator = 194 AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in); 195 196 mProgramDescriptionFadeInAnimator = 197 AnimatorInflater.loadAnimator( 198 mMainActivity, R.animator.channel_banner_program_description_fade_in); 199 mProgramDescriptionFadeOutAnimator = 200 AnimatorInflater.loadAnimator( 201 mMainActivity, R.animator.channel_banner_program_description_fade_out); 202 203 if (CommonFeatures.DVR.isEnabled(mMainActivity)) { 204 mDvrManager = TvSingletons.getSingletons(mMainActivity).getDvrManager(); 205 } else { 206 mDvrManager = null; 207 } 208 mContentRatingsManager = 209 TvSingletons.getSingletons(getContext()) 210 .getTvInputManagerHelper() 211 .getContentRatingsManager(); 212 213 mNoProgram = 214 new Program.Builder() 215 .setTitle(context.getString(R.string.channel_banner_no_title)) 216 .setDescription(EMPTY_STRING) 217 .build(); 218 mLockedChannelProgram = 219 new Program.Builder() 220 .setTitle(context.getString(R.string.channel_banner_locked_channel_title)) 221 .setDescription(EMPTY_STRING) 222 .build(); 223 if (sClosedCaptionMark == null) { 224 sClosedCaptionMark = context.getString(R.string.closed_caption); 225 } 226 mAutoHideScheduler = new AutoHideScheduler(context, this::hide); 227 context.getSystemService(AccessibilityManager.class) 228 .addAccessibilityStateChangeListener(mAutoHideScheduler); 229 } 230 231 @Override onFinishInflate()232 protected void onFinishInflate() { 233 super.onFinishInflate(); 234 235 mChannelView = findViewById(R.id.channel_banner_view); 236 237 mChannelNumberTextView = (TextView) findViewById(R.id.channel_number); 238 mChannelLogoImageView = (ImageView) findViewById(R.id.channel_logo); 239 mProgramTextView = (TextView) findViewById(R.id.program_text); 240 mTvInputLogoImageView = (ImageView) findViewById(R.id.tvinput_logo); 241 mChannelNameTextView = (TextView) findViewById(R.id.channel_name); 242 mProgramTimeTextView = (TextView) findViewById(R.id.program_time_text); 243 mRemainingTimeView = (ProgressBar) findViewById(R.id.remaining_time); 244 mRecordingIndicatorView = (TextView) findViewById(R.id.recording_indicator); 245 mClosedCaptionTextView = (TextView) findViewById(R.id.closed_caption); 246 mAspectRatioTextView = (TextView) findViewById(R.id.aspect_ratio); 247 mResolutionTextView = (TextView) findViewById(R.id.resolution); 248 mAudioChannelTextView = (TextView) findViewById(R.id.audio_channel); 249 mContentRatingsTextViews[0] = (TextView) findViewById(R.id.content_ratings_0); 250 mContentRatingsTextViews[1] = (TextView) findViewById(R.id.content_ratings_1); 251 mContentRatingsTextViews[2] = (TextView) findViewById(R.id.content_ratings_2); 252 mProgramDescriptionTextView = (TextView) findViewById(R.id.program_description); 253 mAnchorView = findViewById(R.id.anchor); 254 255 mProgramDescriptionFadeInAnimator.setTarget(mProgramDescriptionTextView); 256 mProgramDescriptionFadeOutAnimator.setTarget(mProgramDescriptionTextView); 257 mProgramDescriptionFadeOutAnimator.addListener( 258 new AnimatorListenerAdapter() { 259 @Override 260 public void onAnimationEnd(Animator animator) { 261 mProgramDescriptionTextView.setText(mProgramDescriptionText); 262 } 263 }); 264 } 265 266 @Override onEnterAction(boolean fromEmptyScene)267 public void onEnterAction(boolean fromEmptyScene) { 268 resetAnimationEffects(); 269 if (fromEmptyScene) { 270 ViewUtils.setTransitionAlpha(mChannelView, 1f); 271 } 272 mAutoHideScheduler.schedule(mShowDurationMillis); 273 } 274 275 @Override onExitAction()276 public void onExitAction() { 277 mCurrentHeight = 0; 278 mAutoHideScheduler.cancel(); 279 } 280 resetAnimationEffects()281 private void resetAnimationEffects() { 282 setAlpha(1f); 283 setScaleX(1f); 284 setScaleY(1f); 285 setTranslationX(0); 286 setTranslationY(0); 287 } 288 289 /** 290 * Set new lock type. 291 * 292 * @param lockType Any of LOCK_NONE, LOCK_PROGRAM_DETAIL, or LOCK_CHANNEL_INFO. 293 * @return the previous lock type of the channel banner. 294 * @throws IllegalArgumentException if lockType is invalid. 295 */ setLockType(int lockType)296 public int setLockType(int lockType) { 297 if (lockType != LOCK_NONE 298 && lockType != LOCK_CHANNEL_INFO 299 && lockType != LOCK_PROGRAM_DETAIL) { 300 throw new IllegalArgumentException("No such lock type " + lockType); 301 } 302 int previousLockType = mLockType; 303 mLockType = lockType; 304 return previousLockType; 305 } 306 307 /** 308 * Sets the content rating that blocks the current watched channel for displaying it in the 309 * channel banner. 310 */ setBlockingContentRating(TvContentRating rating)311 public void setBlockingContentRating(TvContentRating rating) { 312 mBlockingContentRating = rating; 313 updateProgramRatings(mMainActivity.getCurrentProgram()); 314 } 315 316 /** 317 * Update channel banner view. 318 * 319 * @param updateOnTune {@false} denotes the channel banner is updated due to other reasons than 320 * tuning. The channel info will not be updated in this case. 321 */ updateViews(boolean updateOnTune)322 public void updateViews(boolean updateOnTune) { 323 resetAnimationEffects(); 324 mChannelView.setVisibility(VISIBLE); 325 mUpdateOnTune = updateOnTune; 326 if (mUpdateOnTune) { 327 if (isShown()) { 328 mAutoHideScheduler.schedule(mShowDurationMillis); 329 } 330 mBlockingContentRating = null; 331 mCurrentChannel = mMainActivity.getCurrentChannel(); 332 mCurrentChannelLogoExists = 333 mCurrentChannel != null && mCurrentChannel.channelLogoExists(); 334 updateStreamInfo(null); 335 updateChannelInfo(); 336 } 337 updateProgramInfo(mMainActivity.getCurrentProgram()); 338 mUpdateOnTune = false; 339 } 340 hide()341 private void hide() { 342 mCurrentHeight = 0; 343 mMainActivity 344 .getOverlayManager() 345 .hideOverlays( 346 TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG 347 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS 348 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_PROGRAM_GUIDE 349 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_MENU 350 | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT); 351 } 352 353 /** 354 * Update channel banner view with stream info. 355 * 356 * @param info A StreamInfo that includes stream information. 357 */ updateStreamInfo(StreamInfo info)358 public void updateStreamInfo(StreamInfo info) { 359 // Update stream information in a channel. 360 if (mLockType != LOCK_CHANNEL_INFO && info != null) { 361 updateText( 362 mClosedCaptionTextView, 363 info.hasClosedCaption() ? sClosedCaptionMark : EMPTY_STRING); 364 updateText( 365 mAspectRatioTextView, 366 Utils.getAspectRatioString(info.getVideoDisplayAspectRatio())); 367 updateText( 368 mResolutionTextView, 369 Utils.getVideoDefinitionLevelString( 370 mMainActivity, info.getVideoDefinitionLevel())); 371 updateText( 372 mAudioChannelTextView, 373 Utils.getAudioChannelString(mMainActivity, info.getAudioChannelCount())); 374 } else { 375 // Channel change has been requested. But, StreamInfo hasn't been updated yet. 376 mClosedCaptionTextView.setVisibility(View.GONE); 377 mAspectRatioTextView.setVisibility(View.GONE); 378 mResolutionTextView.setVisibility(View.GONE); 379 mAudioChannelTextView.setVisibility(View.GONE); 380 } 381 } 382 updateChannelInfo()383 private void updateChannelInfo() { 384 // Update static information for a channel. 385 String displayNumber = EMPTY_STRING; 386 String displayName = EMPTY_STRING; 387 if (mCurrentChannel != null) { 388 displayNumber = mCurrentChannel.getDisplayNumber(); 389 if (displayNumber == null) { 390 displayNumber = EMPTY_STRING; 391 } 392 displayName = mCurrentChannel.getDisplayName(); 393 if (displayName == null) { 394 displayName = EMPTY_STRING; 395 } 396 } 397 398 if (displayNumber.isEmpty()) { 399 mChannelNumberTextView.setVisibility(GONE); 400 } else { 401 mChannelNumberTextView.setVisibility(VISIBLE); 402 } 403 if (displayNumber.length() <= 3) { 404 updateTextView( 405 mChannelNumberTextView, 406 R.dimen.channel_banner_channel_number_large_text_size, 407 R.dimen.channel_banner_channel_number_large_margin_top); 408 } else if (displayNumber.length() <= 4) { 409 updateTextView( 410 mChannelNumberTextView, 411 R.dimen.channel_banner_channel_number_medium_text_size, 412 R.dimen.channel_banner_channel_number_medium_margin_top); 413 } else { 414 updateTextView( 415 mChannelNumberTextView, 416 R.dimen.channel_banner_channel_number_small_text_size, 417 R.dimen.channel_banner_channel_number_small_margin_top); 418 } 419 mChannelNumberTextView.setText(displayNumber); 420 mChannelNameTextView.setText(displayName); 421 TvInputInfo info = 422 mMainActivity.getTvInputManagerHelper().getTvInputInfo(getCurrentInputId()); 423 if (info == null 424 || !ImageLoader.loadBitmap( 425 createTvInputLogoLoaderCallback(info, this), 426 new LoadTvInputLogoTask(getContext(), ImageCache.getInstance(), info))) { 427 mTvInputLogoImageView.setVisibility(View.GONE); 428 mTvInputLogoImageView.setImageDrawable(null); 429 } 430 mChannelLogoImageView.setImageBitmap(null); 431 mChannelLogoImageView.setVisibility(View.GONE); 432 if (mCurrentChannel != null && mCurrentChannelLogoExists) { 433 mCurrentChannel.loadBitmap( 434 getContext(), 435 Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, 436 mChannelLogoImageViewWidth, 437 mChannelLogoImageViewHeight, 438 createChannelLogoCallback(this, mCurrentChannel)); 439 } 440 } 441 getCurrentInputId()442 private String getCurrentInputId() { 443 Channel channel = mMainActivity.getCurrentChannel(); 444 return channel != null ? channel.getInputId() : null; 445 } 446 updateTvInputLogo(Bitmap bitmap)447 private void updateTvInputLogo(Bitmap bitmap) { 448 mTvInputLogoImageView.setVisibility(View.VISIBLE); 449 mTvInputLogoImageView.setImageBitmap(bitmap); 450 } 451 createTvInputLogoLoaderCallback( final TvInputInfo info, ChannelBannerView channelBannerView)452 private static ImageLoaderCallback<ChannelBannerView> createTvInputLogoLoaderCallback( 453 final TvInputInfo info, ChannelBannerView channelBannerView) { 454 return new ImageLoaderCallback<ChannelBannerView>(channelBannerView) { 455 @Override 456 public void onBitmapLoaded(ChannelBannerView channelBannerView, Bitmap bitmap) { 457 if (bitmap != null 458 && channelBannerView.mCurrentChannel != null 459 && info.getId().equals(channelBannerView.mCurrentChannel.getInputId())) { 460 channelBannerView.updateTvInputLogo(bitmap); 461 } 462 } 463 }; 464 } 465 466 private void updateText(TextView view, String text) { 467 if (TextUtils.isEmpty(text)) { 468 view.setVisibility(View.GONE); 469 } else { 470 view.setVisibility(View.VISIBLE); 471 view.setText(text); 472 } 473 } 474 475 private void updateTextView(TextView textView, int sizeRes, int marginTopRes) { 476 float textSize = mResources.getDimension(sizeRes); 477 if (textView.getTextSize() != textSize) { 478 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 479 } 480 updateTopMargin(textView, marginTopRes); 481 } 482 483 private void updateTopMargin(View view, int marginTopRes) { 484 RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) view.getLayoutParams(); 485 int topMargin = (int) mResources.getDimension(marginTopRes); 486 if (lp.topMargin != topMargin) { 487 lp.topMargin = topMargin; 488 view.setLayoutParams(lp); 489 } 490 } 491 492 private static ImageLoaderCallback<ChannelBannerView> createChannelLogoCallback( 493 ChannelBannerView channelBannerView, final Channel channel) { 494 return new ImageLoaderCallback<ChannelBannerView>(channelBannerView) { 495 @Override 496 public void onBitmapLoaded(ChannelBannerView view, @Nullable Bitmap logo) { 497 if (channel.equals(view.mCurrentChannel)) { 498 // The logo is obsolete. 499 return; 500 } 501 view.updateLogo(logo); 502 } 503 }; 504 } 505 506 private void updateLogo(@Nullable Bitmap logo) { 507 if (logo == null) { 508 // Need to update the text size of the program text view depending on the channel logo. 509 updateProgramTextView(mLastUpdatedProgram); 510 return; 511 } 512 513 mChannelLogoImageView.setImageBitmap(logo); 514 mChannelLogoImageView.setVisibility(View.VISIBLE); 515 updateProgramTextView(mLastUpdatedProgram); 516 517 if (mResizeAnimator == null) { 518 String description = mProgramDescriptionTextView.getText().toString(); 519 boolean programDescriptionNeedFadeAnimation = 520 !description.equals(mProgramDescriptionText) && !mUpdateOnTune; 521 updateBannerHeight(programDescriptionNeedFadeAnimation); 522 } else { 523 mProgramInfoUpdatePendingByResizing = true; 524 } 525 } 526 527 private void updateProgramInfo(Program program) { 528 if (mLockType == LOCK_CHANNEL_INFO) { 529 program = mLockedChannelProgram; 530 } else if (program == null || !program.isValid() || TextUtils.isEmpty(program.getTitle())) { 531 program = mNoProgram; 532 } 533 534 if (mLastUpdatedProgram == null 535 || !TextUtils.equals(program.getTitle(), mLastUpdatedProgram.getTitle()) 536 || !TextUtils.equals( 537 program.getEpisodeDisplayTitle(getContext()), 538 mLastUpdatedProgram.getEpisodeDisplayTitle(getContext()))) { 539 updateProgramTextView(program); 540 } 541 updateProgramTimeInfo(program); 542 updateRecordingStatus(program); 543 updateProgramRatings(program); 544 545 // When the program is changed, but the previous resize animation has not ended yet, 546 // cancel the animation. 547 boolean isProgramChanged = !program.equals(mLastUpdatedProgram); 548 if (mResizeAnimator != null && isProgramChanged) { 549 setLastUpdatedProgram(program); 550 mProgramInfoUpdatePendingByResizing = true; 551 mResizeAnimator.cancel(); 552 } else if (mResizeAnimator == null) { 553 if (mLockType != LOCK_NONE || TextUtils.isEmpty(program.getDescription())) { 554 mProgramDescriptionTextView.setVisibility(GONE); 555 mProgramDescriptionText = ""; 556 } else { 557 mProgramDescriptionTextView.setVisibility(VISIBLE); 558 mProgramDescriptionText = program.getDescription(); 559 } 560 String description = mProgramDescriptionTextView.getText().toString(); 561 boolean programDescriptionNeedFadeAnimation = 562 (isProgramChanged || !description.equals(mProgramDescriptionText)) 563 && !mUpdateOnTune; 564 updateBannerHeight(programDescriptionNeedFadeAnimation); 565 } else { 566 mProgramInfoUpdatePendingByResizing = true; 567 } 568 setLastUpdatedProgram(program); 569 } 570 571 private void updateProgramTextView(Program program) { 572 if (program == null) { 573 return; 574 } 575 updateProgramTextView( 576 program.equals(mLockedChannelProgram), 577 program.getTitle(), 578 program.getEpisodeDisplayTitle(getContext())); 579 } 580 581 private void updateProgramTextView(boolean dimText, String title, String episodeDisplayTitle) { 582 mProgramTextView.setVisibility(View.VISIBLE); 583 if (dimText) { 584 mProgramTextView.setTextColor(mChannelBannerDimTextColor); 585 } else { 586 mProgramTextView.setTextColor(mChannelBannerTextColor); 587 } 588 updateTextView( 589 mProgramTextView, 590 R.dimen.channel_banner_program_large_text_size, 591 R.dimen.channel_banner_program_large_margin_top); 592 if (TextUtils.isEmpty(episodeDisplayTitle)) { 593 mProgramTextView.setText(title); 594 } else { 595 String fullTitle = title + " " + episodeDisplayTitle; 596 597 SpannableString text = new SpannableString(fullTitle); 598 text.setSpan( 599 new TextAppearanceSpan( 600 getContext(), R.style.text_appearance_channel_banner_episode_title), 601 fullTitle.length() - episodeDisplayTitle.length(), 602 fullTitle.length(), 603 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 604 mProgramTextView.setText(text); 605 } 606 int width = 607 mProgramDescriptionTextViewWidth 608 + (mCurrentChannelLogoExists 609 ? 0 610 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart); 611 ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams(); 612 lp.width = width; 613 mProgramTextView.setLayoutParams(lp); 614 mProgramTextView.measure( 615 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 616 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 617 618 boolean oneline = (mProgramTextView.getLineCount() == 1); 619 if (!oneline) { 620 updateTextView( 621 mProgramTextView, 622 R.dimen.channel_banner_program_medium_text_size, 623 R.dimen.channel_banner_program_medium_margin_top); 624 mProgramTextView.measure( 625 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), 626 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 627 oneline = (mProgramTextView.getLineCount() == 1); 628 } 629 updateTopMargin( 630 mAnchorView, 631 oneline 632 ? R.dimen.channel_banner_anchor_one_line_y 633 : R.dimen.channel_banner_anchor_two_line_y); 634 } 635 636 private void updateProgramRatings(Program program) { 637 if (mLockType == LOCK_CHANNEL_INFO) { 638 for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { 639 mContentRatingsTextViews[i].setVisibility(View.GONE); 640 } 641 } else if (mBlockingContentRating != null) { 642 String displayNameForRating = 643 mContentRatingsManager.getDisplayNameForRating(mBlockingContentRating); 644 if (!TextUtils.isEmpty(displayNameForRating)) { 645 mContentRatingsTextViews[0].setText(displayNameForRating); 646 mContentRatingsTextViews[0].setVisibility(View.VISIBLE); 647 } else { 648 mContentRatingsTextViews[0].setVisibility(View.GONE); 649 } 650 for (int i = 1; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { 651 mContentRatingsTextViews[i].setVisibility(View.GONE); 652 } 653 } else { 654 TvContentRating[] ratings = (program == null) ? null : program.getContentRatings(); 655 for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) { 656 if (ratings == null || ratings.length <= i) { 657 mContentRatingsTextViews[i].setVisibility(View.GONE); 658 } else { 659 mContentRatingsTextViews[i].setText( 660 mContentRatingsManager.getDisplayNameForRating(ratings[i])); 661 mContentRatingsTextViews[i].setVisibility(View.VISIBLE); 662 } 663 } 664 } 665 } 666 667 private void updateProgramTimeInfo(Program program) { 668 long durationMs = program.getDurationMillis(); 669 long startTimeMs = program.getStartTimeUtcMillis(); 670 long endTimeMs = program.getEndTimeUtcMillis(); 671 672 if (mLockType != LOCK_CHANNEL_INFO && durationMs > 0 && startTimeMs > 0) { 673 mProgramTimeTextView.setVisibility(View.VISIBLE); 674 mRemainingTimeView.setVisibility(View.VISIBLE); 675 mProgramTimeTextView.setText( 676 Utils.getDurationString(getContext(), startTimeMs, endTimeMs, true)); 677 } else { 678 mProgramTimeTextView.setVisibility(View.GONE); 679 mRemainingTimeView.setVisibility(View.GONE); 680 } 681 } 682 683 private int getProgressPercent(long currTime, long startTime, long endTime) { 684 if (currTime <= startTime) { 685 return 0; 686 } else if (currTime >= endTime) { 687 return 100; 688 } else { 689 return (int) (100 * (currTime - startTime) / (endTime - startTime)); 690 } 691 } 692 693 private void updateRecordingStatus(Program program) { 694 if (mDvrManager == null) { 695 updateProgressBarAndRecIcon(program, null); 696 return; 697 } 698 ScheduledRecording currentRecording = 699 (mCurrentChannel == null) 700 ? null 701 : mDvrManager.getCurrentRecording(mCurrentChannel.getId()); 702 if (DEBUG) { 703 Log.d(TAG, currentRecording == null ? "No Recording" : "Recording:" + currentRecording); 704 } 705 if (currentRecording != null && isCurrentProgram(currentRecording, program)) { 706 updateProgressBarAndRecIcon(program, currentRecording); 707 } else { 708 updateProgressBarAndRecIcon(program, null); 709 } 710 } 711 712 private void updateProgressBarAndRecIcon( 713 Program program, @Nullable ScheduledRecording recording) { 714 long programStartTime = program.getStartTimeUtcMillis(); 715 long programEndTime = program.getEndTimeUtcMillis(); 716 long currentPosition = mMainActivity.getCurrentPlayingPosition(); 717 updateRecordingIndicator(recording); 718 if (recording != null) { 719 // Recording now. Use recording-style progress bar. 720 mRemainingTimeView.setProgress( 721 getProgressPercent( 722 recording.getStartTimeMs(), programStartTime, programEndTime)); 723 mRemainingTimeView.setSecondaryProgress( 724 getProgressPercent(currentPosition, programStartTime, programEndTime)); 725 } else { 726 // No recording is going now. Recover progress bar. 727 mRemainingTimeView.setProgress( 728 getProgressPercent(currentPosition, programStartTime, programEndTime)); 729 mRemainingTimeView.setSecondaryProgress(0); 730 } 731 } 732 733 private void updateRecordingIndicator(@Nullable ScheduledRecording recording) { 734 if (recording != null) { 735 if (mRemainingTimeView.getVisibility() == View.GONE) { 736 mRecordingIndicatorView.setText( 737 mMainActivity 738 .getResources() 739 .getString( 740 R.string.dvr_recording_till_format, 741 DateUtils.formatDateTime( 742 mMainActivity, 743 recording.getEndTimeMs(), 744 DateUtils.FORMAT_SHOW_TIME))); 745 mRecordingIndicatorView.setCompoundDrawablePadding(mRecordingIconPadding); 746 } else { 747 mRecordingIndicatorView.setText(""); 748 mRecordingIndicatorView.setCompoundDrawablePadding(0); 749 } 750 mRecordingIndicatorView.setVisibility(View.VISIBLE); 751 } else { 752 mRecordingIndicatorView.setVisibility(View.GONE); 753 } 754 } 755 756 private boolean isCurrentProgram(ScheduledRecording recording, Program program) { 757 long currentPosition = mMainActivity.getCurrentPlayingPosition(); 758 return (recording.getType() == ScheduledRecording.TYPE_PROGRAM 759 && recording.getProgramId() == program.getId()) 760 || (recording.getType() == ScheduledRecording.TYPE_TIMED 761 && currentPosition >= recording.getStartTimeMs() 762 && currentPosition <= recording.getEndTimeMs()); 763 } 764 765 private void setLastUpdatedProgram(Program program) { 766 mLastUpdatedProgram = program; 767 } 768 769 private void updateBannerHeight(boolean needProgramDescriptionFadeAnimation) { 770 SoftPreconditions.checkState(mResizeAnimator == null); 771 // Need to measure the layout height with the new description text. 772 CharSequence oldDescription = mProgramDescriptionTextView.getText(); 773 mProgramDescriptionTextView.setText(mProgramDescriptionText); 774 measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 775 int targetHeight = getMeasuredHeight(); 776 777 if (mCurrentHeight == 0 || !isShown()) { 778 // Do not add the resize animation when the banner has not been shown before. 779 mCurrentHeight = targetHeight; 780 LayoutParams layoutParams = (LayoutParams) getLayoutParams(); 781 if (targetHeight != layoutParams.height) { 782 layoutParams.height = targetHeight; 783 setLayoutParams(layoutParams); 784 } 785 } else if (mCurrentHeight != targetHeight || needProgramDescriptionFadeAnimation) { 786 // Restore description text for fade in/out animation. 787 if (needProgramDescriptionFadeAnimation) { 788 mProgramDescriptionTextView.setText(oldDescription); 789 } 790 mResizeAnimator = 791 createResizeAnimator(targetHeight, needProgramDescriptionFadeAnimation); 792 mResizeAnimator.start(); 793 } 794 } 795 796 private Animator createResizeAnimator(int targetHeight, boolean addFadeAnimation) { 797 final ValueAnimator heightAnimator = ValueAnimator.ofInt(mCurrentHeight, targetHeight); 798 heightAnimator.addUpdateListener( 799 new ValueAnimator.AnimatorUpdateListener() { 800 @Override 801 public void onAnimationUpdate(ValueAnimator animation) { 802 int value = (Integer) animation.getAnimatedValue(); 803 LayoutParams layoutParams = 804 (LayoutParams) ChannelBannerView.this.getLayoutParams(); 805 if (value != layoutParams.height) { 806 layoutParams.height = value; 807 ChannelBannerView.this.setLayoutParams(layoutParams); 808 } 809 mCurrentHeight = value; 810 } 811 }); 812 813 heightAnimator.setDuration(mResizeAnimDuration); 814 heightAnimator.setInterpolator(mResizeInterpolator); 815 816 if (!addFadeAnimation) { 817 heightAnimator.addListener(mResizeAnimatorListener); 818 return heightAnimator; 819 } 820 821 AnimatorSet fadeOutAndHeightAnimator = new AnimatorSet(); 822 fadeOutAndHeightAnimator.playTogether(mProgramDescriptionFadeOutAnimator, heightAnimator); 823 AnimatorSet animator = new AnimatorSet(); 824 animator.playSequentially(fadeOutAndHeightAnimator, mProgramDescriptionFadeInAnimator); 825 animator.addListener(mResizeAnimatorListener); 826 return animator; 827 } 828 829 @Override 830 public void onAccessibilityStateChanged(boolean enabled) { 831 mAutoHideScheduler.onAccessibilityStateChanged(enabled); 832 } 833 } 834