1 /* 2 * Copyright (C) 2016 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.dvr.ui.browse; 18 19 import android.animation.ValueAnimator; 20 import android.content.Context; 21 import android.graphics.Bitmap; 22 import android.graphics.Rect; 23 import android.graphics.drawable.BitmapDrawable; 24 import android.graphics.drawable.Drawable; 25 import android.support.annotation.Nullable; 26 import android.text.Layout; 27 import android.text.TextUtils; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewTreeObserver; 31 import android.widget.FrameLayout; 32 import android.widget.ImageView; 33 import android.widget.ProgressBar; 34 import android.widget.TextView; 35 import androidx.leanback.widget.BaseCardView; 36 import com.android.tv.R; 37 import com.android.tv.dvr.data.RecordedProgram; 38 import com.android.tv.ui.ViewUtils; 39 import com.android.tv.util.images.ImageLoader; 40 41 /** 42 * A CardView for displaying info about a {@link com.android.tv.dvr.data.ScheduledRecording} or 43 * {@link RecordedProgram} or {@link com.android.tv.dvr.data.SeriesRecording}. 44 */ 45 public class RecordingCardView extends BaseCardView { 46 // This value should be the same with 47 // androidx.leanback.widget.FocusHighlightHelper.BrowseItemFocusHighlight.DURATION_MS 48 private static final int ANIMATION_DURATION = 150; 49 private final ImageView mImageView; 50 private final int mImageWidth; 51 private final int mImageHeight; 52 private String mImageUri; 53 private final ImageView mContentIconView; 54 private final TextView mMajorContentView; 55 private final TextView mMinorContentView; 56 private final ProgressBar mProgressBar; 57 private final Drawable mDefaultImage; 58 private final FrameLayout mTitleArea; 59 private final TextView mFoldedTitleView; 60 private final TextView mExpandedTitleView; 61 private final ValueAnimator mExpandTitleAnimator; 62 private final int mFoldedTitleHeight; 63 private final int mExpandedTitleHeight; 64 private final boolean mExpandTitleWhenFocused; 65 private boolean mExpanded; 66 private String mDetailBackgroundImageUri; 67 private Layout mTitleViewLayout; 68 RecordingCardView(Context context)69 public RecordingCardView(Context context) { 70 this(context, false); 71 } 72 RecordingCardView(Context context, boolean expandTitleWhenFocused)73 public RecordingCardView(Context context, boolean expandTitleWhenFocused) { 74 this( 75 context, 76 context.getResources() 77 .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_width), 78 context.getResources() 79 .getDimensionPixelSize(R.dimen.dvr_library_card_image_layout_height), 80 expandTitleWhenFocused); 81 } 82 RecordingCardView( Context context, int imageWidth, int imageHeight, boolean expandTitleWhenFocused)83 public RecordingCardView( 84 Context context, int imageWidth, int imageHeight, boolean expandTitleWhenFocused) { 85 super(context); 86 // TODO(dvr): move these to the layout XML. 87 setCardType(BaseCardView.CARD_TYPE_INFO_UNDER_WITH_EXTRA); 88 setInfoVisibility(BaseCardView.CARD_REGION_VISIBLE_ALWAYS); 89 setFocusable(true); 90 setFocusableInTouchMode(true); 91 mDefaultImage = getResources().getDrawable(R.drawable.dvr_default_poster, null); 92 93 LayoutInflater inflater = LayoutInflater.from(getContext()); 94 inflater.inflate(R.layout.dvr_recording_card_view, this); 95 mImageView = (ImageView) findViewById(R.id.image); 96 mImageWidth = imageWidth; 97 mImageHeight = imageHeight; 98 mProgressBar = (ProgressBar) findViewById(R.id.recording_progress); 99 mContentIconView = (ImageView) findViewById(R.id.content_icon); 100 mMajorContentView = (TextView) findViewById(R.id.content_major); 101 mMinorContentView = (TextView) findViewById(R.id.content_minor); 102 mTitleArea = (FrameLayout) findViewById(R.id.title_area); 103 mFoldedTitleView = (TextView) findViewById(R.id.title_one_line); 104 mExpandedTitleView = (TextView) findViewById(R.id.title_two_lines); 105 mFoldedTitleHeight = 106 getResources().getDimensionPixelSize(R.dimen.dvr_library_card_folded_title_height); 107 mExpandedTitleHeight = 108 getResources() 109 .getDimensionPixelSize(R.dimen.dvr_library_card_expanded_title_height); 110 mExpandTitleAnimator = ValueAnimator.ofFloat(0.0f, 1.0f).setDuration(ANIMATION_DURATION); 111 mExpandTitleAnimator.addUpdateListener( 112 new ValueAnimator.AnimatorUpdateListener() { 113 @Override 114 public void onAnimationUpdate(ValueAnimator valueAnimator) { 115 float value = (Float) valueAnimator.getAnimatedValue(); 116 mExpandedTitleView.setAlpha(value); 117 mFoldedTitleView.setAlpha(1.0f - value); 118 ViewUtils.setLayoutHeight( 119 mTitleArea, 120 (int) 121 (mFoldedTitleHeight 122 + (mExpandedTitleHeight - mFoldedTitleHeight) 123 * value)); 124 } 125 }); 126 getViewTreeObserver().addOnGlobalLayoutListener( 127 new ViewTreeObserver.OnGlobalLayoutListener() { 128 @Override 129 public void onGlobalLayout() { 130 getViewTreeObserver().removeOnGlobalLayoutListener(this); 131 mTitleViewLayout = mFoldedTitleView.getLayout(); 132 } 133 }); 134 mExpandTitleWhenFocused = expandTitleWhenFocused; 135 } 136 137 @Override onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)138 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { 139 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); 140 // Preload the background image going to be used in detail fragments here to prevent 141 // loading and drawing background images during activity transitions. 142 if (gainFocus) { 143 if (!TextUtils.isEmpty(mDetailBackgroundImageUri)) { 144 ImageLoader.loadBitmap( 145 getContext(), 146 mDetailBackgroundImageUri, 147 Integer.MAX_VALUE, 148 Integer.MAX_VALUE, 149 null); 150 } 151 } 152 if (mExpandTitleWhenFocused) { 153 if (gainFocus) { 154 expandTitle(true, true); 155 } else { 156 expandTitle(false, true); 157 } 158 } 159 } 160 161 /** 162 * Expands/folds the title area to show program title with two/one lines. 163 * 164 * @param expand {@code true} to expand the title area, or {@code false} to fold it. 165 * @param withAnimation {@code true} to expand/fold with animation. 166 */ expandTitle(boolean expand, boolean withAnimation)167 public void expandTitle(boolean expand, boolean withAnimation) { 168 if (expand != mExpanded && mTitleViewLayout != null 169 && mTitleViewLayout.getEllipsisCount(0) > 0) { 170 if (withAnimation) { 171 if (expand) { 172 mExpandTitleAnimator.start(); 173 } else { 174 mExpandTitleAnimator.reverse(); 175 } 176 } else { 177 if (expand) { 178 mFoldedTitleView.setAlpha(0.0f); 179 mExpandedTitleView.setAlpha(1.0f); 180 ViewUtils.setLayoutHeight(mTitleArea, mExpandedTitleHeight); 181 } else { 182 mFoldedTitleView.setAlpha(1.0f); 183 mExpandedTitleView.setAlpha(0.0f); 184 ViewUtils.setLayoutHeight(mTitleArea, mFoldedTitleHeight); 185 } 186 } 187 mExpanded = expand; 188 } 189 } 190 setTitle(CharSequence title)191 void setTitle(CharSequence title) { 192 mFoldedTitleView.setText(title); 193 mExpandedTitleView.setText(title); 194 } 195 setContent(CharSequence majorContent, CharSequence minorContent)196 void setContent(CharSequence majorContent, CharSequence minorContent) { 197 mContentIconView.setVisibility(View.GONE); 198 if (!TextUtils.isEmpty(majorContent)) { 199 mMajorContentView.setText(majorContent); 200 mMajorContentView.setVisibility(View.VISIBLE); 201 } else { 202 mMajorContentView.setVisibility(View.GONE); 203 } 204 if (!TextUtils.isEmpty(minorContent)) { 205 mMinorContentView.setText(minorContent); 206 mMinorContentView.setVisibility(View.VISIBLE); 207 } else { 208 mMinorContentView.setVisibility(View.GONE); 209 } 210 } 211 setRecordingFailedContent(Context context)212 void setRecordingFailedContent(Context context) { 213 mContentIconView.setVisibility(View.VISIBLE); 214 mContentIconView.setImageResource(R.drawable.ic_error_outline_pink_24dp); 215 mMajorContentView.setText(context.getString(R.string.dvr_recording_failed_no_period)); 216 mMajorContentView.setVisibility(View.VISIBLE); 217 mMajorContentView.setTextColor( 218 getResources().getColor(R.color.dvr_recording_failed_text_color, null)); 219 } 220 setRecordingConflictContent(Context context)221 void setRecordingConflictContent(Context context) { 222 mContentIconView.setVisibility(View.VISIBLE); 223 mContentIconView.setImageResource(R.drawable.ic_warning_yellow_24dp); 224 mMajorContentView.setText(context.getString(R.string.dvr_recording_conflict)); 225 mMajorContentView.setVisibility(View.VISIBLE); 226 mMajorContentView.setTextColor( 227 getResources().getColor(R.color.dvr_recording_conflict_text_color, null)); 228 } 229 230 /** Sets progress bar. If progress is {@code null}, hides progress bar. */ setProgressBar(Integer progress)231 void setProgressBar(Integer progress) { 232 if (progress == null) { 233 mProgressBar.setVisibility(View.GONE); 234 } else { 235 mProgressBar.setProgress(progress); 236 mProgressBar.setVisibility(View.VISIBLE); 237 } 238 } 239 240 /** Sets the color of progress bar. */ setProgressBarColor(int color)241 void setProgressBarColor(int color) { 242 mProgressBar.getProgressDrawable().setTint(color); 243 } 244 245 /** 246 * Sets the image URI of the poster should be shown on the card view. 247 * 248 * @param isChannelLogo {@code true} if the image is from channels' logo. 249 */ setImageUri(String uri, boolean isChannelLogo)250 void setImageUri(String uri, boolean isChannelLogo) { 251 if (isChannelLogo) { 252 mImageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 253 } else { 254 mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP); 255 } 256 mImageUri = uri; 257 if (TextUtils.isEmpty(uri)) { 258 mImageView.setImageDrawable(mDefaultImage); 259 } else { 260 ImageLoader.loadBitmap( 261 getContext(), 262 uri, 263 mImageWidth, 264 mImageHeight, 265 new RecordingCardImageLoaderCallback(this, uri)); 266 } 267 } 268 269 /** Sets the {@link Drawable} of the poster should be shown on the card view. */ setImage(Drawable image)270 public void setImage(Drawable image) { 271 if (image != null) { 272 mImageView.setImageDrawable(image); 273 } 274 } 275 276 /** 277 * Sets the background image URI of the card view, which will be displayed as background when 278 * the view is clicked and shows its details fragment. 279 */ setDetailBackgroundImageUri(String uri)280 public void setDetailBackgroundImageUri(String uri) { 281 mDetailBackgroundImageUri = uri; 282 } 283 284 /** Returns image view. */ getImageView()285 public ImageView getImageView() { 286 return mImageView; 287 } 288 289 private static class RecordingCardImageLoaderCallback 290 extends ImageLoader.ImageLoaderCallback<RecordingCardView> { 291 private final String mUri; 292 RecordingCardImageLoaderCallback(RecordingCardView referent, String uri)293 RecordingCardImageLoaderCallback(RecordingCardView referent, String uri) { 294 super(referent); 295 mUri = uri; 296 } 297 298 @Override onBitmapLoaded(RecordingCardView view, @Nullable Bitmap bitmap)299 public void onBitmapLoaded(RecordingCardView view, @Nullable Bitmap bitmap) { 300 if (bitmap == null || !mUri.equals(view.mImageUri)) { 301 view.mImageView.setImageDrawable(view.mDefaultImage); 302 } else { 303 view.mImageView.setImageDrawable(new BitmapDrawable(view.getResources(), bitmap)); 304 } 305 } 306 } 307 reset()308 public void reset() { 309 mFoldedTitleView.setText(null); 310 mExpandedTitleView.setText(null); 311 setContent(null, null); 312 mImageView.setImageDrawable(mDefaultImage); 313 } 314 } 315