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