Home | History | Annotate | Download | only in ui
      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 }