ChannelBannerView.java revision 4e5a6a47adb40039a27536236656204f9ad12df3
1/* 2 * Copyright (C) 2014 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 17package com.android.tv.ui; 18 19import android.content.Context; 20import android.database.ContentObserver; 21import android.graphics.Bitmap; 22import android.graphics.Paint; 23import android.graphics.Typeface; 24import android.graphics.drawable.Drawable; 25import android.media.tv.TvContentRating; 26import android.media.tv.TvContract; 27import android.media.tv.TvInputInfo; 28import android.media.tv.TvInputManager; 29import android.net.Uri; 30import android.os.Handler; 31import android.text.TextUtils; 32import android.text.format.DateFormat; 33import android.util.AttributeSet; 34import android.util.LruCache; 35import android.util.TypedValue; 36import android.view.View; 37import android.widget.ImageView; 38import android.widget.ProgressBar; 39import android.widget.RelativeLayout; 40import android.widget.TextView; 41 42import com.android.tv.R; 43import com.android.tv.data.Channel; 44import com.android.tv.data.ChannelMap; 45import com.android.tv.data.Program; 46import com.android.tv.data.StreamInfo; 47import com.android.tv.util.Utils; 48 49/** 50 * A view to render channel banner. 51 */ 52public class ChannelBannerView extends RelativeLayout implements Channel.LoadLogoCallback { 53 private static final int CACHE_SIZE = 10; 54 private TextView mChannelTextView; 55 private ImageView mChannelLogoImageView; 56 private TextView mProgramTextView; 57 private ImageView mTvInputLogoImageView; 58 private TextView mChannelNameTextView; 59 private TextView mProgramTimeTextView; 60 private ProgressBar mRemainingTimeView; 61 private TextView mVideoStatus; 62 // TODO: Need to get UX design for how to show ratings in the channel banner. 63 private TextView mRatingTextView; 64 private TextView mClosedCaptionTextView; 65 private TextView mAspectRatioTextView; 66 private TextView mResolutionTextView; 67 private TextView mAudioChannelTextView; 68 private TextView mProgrameDescriptionTextView; 69 private View mAnchorView; 70 private Uri mCurrentChannelUri; 71 private final LruCache<TvInputInfo, Drawable> mChannelInfoLogoCache = 72 new LruCache<TvInputInfo, Drawable> (CACHE_SIZE) { 73 @Override 74 protected Drawable create(TvInputInfo info) { 75 return info.loadIcon(getContext()); 76 } 77 }; 78 79 private final ContentObserver mProgramUpdateObserver = new ContentObserver(new Handler()) { 80 @Override 81 public void onChange(boolean selfChange, Uri uri) { 82 // TODO: This {@code uri} argument may be a program which is not related to this 83 // channel. Consider adding channel id as a parameter of program URI to avoid 84 // unnecessary update. 85 post(mProgramUpdateRunnable); 86 } 87 }; 88 89 private final Runnable mProgramUpdateRunnable = new Runnable() { 90 @Override 91 public void run() { 92 removeCallbacks(this); 93 updateProgramInfo(); 94 } 95 }; 96 97 public ChannelBannerView(Context context) { 98 this(context, null); 99 } 100 101 public ChannelBannerView(Context context, AttributeSet attrs) { 102 super(context, attrs, 0); 103 } 104 105 public ChannelBannerView(Context context, AttributeSet attrs, int defStyle) { 106 super(context, attrs, defStyle); 107 } 108 109 @Override 110 protected void onAttachedToWindow() { 111 super.onAttachedToWindow(); 112 getContext().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI, 113 true, mProgramUpdateObserver); 114 } 115 116 @Override 117 protected void onDetachedFromWindow() { 118 getContext().getContentResolver().unregisterContentObserver(mProgramUpdateObserver); 119 super.onDetachedFromWindow(); 120 } 121 122 @Override 123 protected void onFinishInflate() { 124 super.onFinishInflate(); 125 126 mChannelTextView = (TextView) findViewById(R.id.channel_text); 127 mChannelLogoImageView = (ImageView) findViewById(R.id.channel_logo); 128 mProgramTextView = (TextView) findViewById(R.id.program_text); 129 mTvInputLogoImageView = (ImageView) findViewById(R.id.tvinput_logo); 130 mChannelNameTextView = (TextView) findViewById(R.id.channel_name); 131 mProgramTimeTextView = (TextView) findViewById(R.id.program_time_text); 132 mRemainingTimeView = (ProgressBar) findViewById(R.id.remaining_time); 133 mVideoStatus = (TextView) findViewById(R.id.video_status); 134 mRatingTextView = (TextView) findViewById(R.id.rating); 135 mClosedCaptionTextView = (TextView) findViewById(R.id.closed_caption); 136 mAspectRatioTextView = (TextView) findViewById(R.id.aspect_ratio); 137 mResolutionTextView = (TextView) findViewById(R.id.resolution); 138 mAudioChannelTextView = (TextView) findViewById(R.id.audio_channel); 139 mProgrameDescriptionTextView = (TextView) findViewById(R.id.program_description); 140 mAnchorView = findViewById(R.id.anchor); 141 } 142 143 public void updateViews(ChannelMap channelMap, StreamInfo info) { 144 if (channelMap == null || !channelMap.isLoadFinished()) { 145 return; 146 } 147 148 String displayNumber = channelMap.getCurrentDisplayNumber(); 149 if (displayNumber == null) { 150 displayNumber = ""; 151 } 152 153 if (displayNumber.length() <= 3) { 154 updateTextView( 155 mChannelTextView, 156 R.dimen.channel_banner_title_large_text_size, 157 R.dimen.channel_banner_title_large_margin_top); 158 } else if (displayNumber.length() <= 4) { 159 updateTextView( 160 mChannelTextView, 161 R.dimen.channel_banner_title_medium_text_size, 162 R.dimen.channel_banner_title_medium_margin_top); 163 } else { 164 updateTextView( 165 mChannelTextView, 166 R.dimen.channel_banner_title_small_text_size, 167 R.dimen.channel_banner_title_small_margin_top); 168 } 169 mChannelTextView.setText(displayNumber); 170 171 TvInputInfo inputInfo = (info == null) ? null : info.getCurrentTvInputInfo(); 172 Drawable tvInputLogo = (inputInfo == null) ? null : mChannelInfoLogoCache.get(inputInfo); 173 if (tvInputLogo != null) { 174 mTvInputLogoImageView.setVisibility(View.VISIBLE); 175 mTvInputLogoImageView.setImageDrawable(tvInputLogo); 176 } else { 177 mTvInputLogoImageView.setVisibility(View.GONE); 178 } 179 180 String displayName = channelMap.getCurrentDisplayName(); 181 if (displayName == null) { 182 displayName = ""; 183 } 184 mChannelNameTextView.setText(displayName); 185 186 int resId = 0; 187 if (!info.isVideoAvailable()) { 188 switch (info.getVideoUnavailableReason()) { 189 case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNE: 190 // We don't need to tell we're tuning. 191 break; 192 case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL: 193 resId = R.string.channel_banner_video_unavailable_weak_signal; 194 break; 195 case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING: 196 resId = R.string.channel_banner_video_unavailable_buffering; 197 break; 198 default: 199 resId = R.string.channel_banner_video_unavailable_unknown; 200 break; 201 } 202 } 203 updateText(mVideoStatus, (resId == 0) ? null : getContext().getString(resId)); 204 205 updateText(mClosedCaptionTextView, info.hasClosedCaption() ? "CC" : ""); 206 207 updateText(mAspectRatioTextView, 208 Utils.getAspectRatioString(info.getVideoWidth(), info.getVideoHeight())); 209 210 updateText(mResolutionTextView, 211 Utils.getVideoDefinitionLevelString(info.getVideoDefinitionLevel())); 212 213 updateText(mAudioChannelTextView, 214 Utils.getAudioChannelString(info.getAudioChannelCount())); 215 216 mCurrentChannelUri = channelMap.getCurrentChannelUri(); 217 if (channelMap.getCurrentChannel() != null) { 218 channelMap.getCurrentChannel().loadLogo(getContext(), this); 219 } 220 221 updateProgramInfo(); 222 } 223 224 private void updateText(TextView view, String text) { 225 if (TextUtils.isEmpty(text)) { 226 view.setVisibility(View.GONE); 227 } else { 228 view.setVisibility(View.VISIBLE); 229 view.setText(text); 230 } 231 } 232 233 private void updateTextView(TextView textView, int sizeRes, int marginTopRes) { 234 float textSize = getContext().getResources().getDimension(sizeRes); 235 if (textView.getTextSize() != textSize) { 236 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 237 } 238 updateTopMargin(textView, marginTopRes); 239 } 240 241 private void updateTopMargin(View view, int marginTopRes) { 242 LayoutParams lp = (LayoutParams) view.getLayoutParams(); 243 int topMargin = (int) getContext().getResources().getDimension(marginTopRes); 244 if (lp.topMargin != topMargin) { 245 lp.topMargin = topMargin; 246 view.setLayoutParams(lp); 247 } 248 } 249 250 @Override 251 public void onLoadLogoFinished(Channel channel, Bitmap logo) { 252 if (logo == null) { 253 mChannelLogoImageView.setVisibility(View.GONE); 254 } else { 255 mChannelLogoImageView.setImageBitmap(logo); 256 mChannelLogoImageView.setVisibility(View.VISIBLE); 257 } 258 } 259 260 private String getFormattedTimeString(long time) { 261 return DateFormat.format( 262 getContext().getString(R.string.channel_banner_time_format), time).toString(); 263 } 264 265 private void updateProgramInfo() { 266 if (mCurrentChannelUri == null) { 267 handleNoProgramInformation(); 268 return; 269 } 270 271 Program program = Utils.getCurrentProgram(getContext(), mCurrentChannelUri); 272 if (program == null) { 273 handleNoProgramInformation(); 274 return; 275 } 276 277 String title = program.getTitle(); 278 if (!TextUtils.isEmpty(title)) { 279 int width = mProgramTextView.getWidth(); 280 if (width == 0) { 281 post(mProgramUpdateRunnable); 282 } 283 float largeTextSize = getContext().getResources().getDimension( 284 R.dimen.channel_banner_program_large_text_size); 285 Typeface font = mProgramTextView.getTypeface(); 286 int estimatedLineCount = estimateLineCount(title, font, largeTextSize, width); 287 boolean oneline = true; 288 if (estimatedLineCount > 1) { 289 updateTextView( 290 mProgramTextView, 291 R.dimen.channel_banner_program_medium_text_size, 292 R.dimen.channel_banner_program_medium_margin_top); 293 float mediumTextSize = getContext().getResources().getDimension( 294 R.dimen.channel_banner_program_medium_text_size); 295 if (estimateLineCount(title, font, mediumTextSize, width) > 1) { 296 oneline = false; 297 } 298 } else { 299 updateTextView( 300 mProgramTextView, 301 R.dimen.channel_banner_program_large_text_size, 302 R.dimen.channel_banner_program_large_margin_top); 303 } 304 updateTopMargin(mAnchorView, oneline 305 ? R.dimen.channel_banner_anchor_one_line_y 306 : R.dimen.channel_banner_anchor_two_line_y); 307 mProgramTextView.setText(title); 308 309 long startTime = program.getStartTimeUtcMillis(); 310 long endTime = program.getEndTimeUtcMillis(); 311 if (startTime > 0 && endTime > 0) { 312 mProgramTimeTextView.setVisibility(View.VISIBLE); 313 mRemainingTimeView.setVisibility(View.VISIBLE); 314 315 String startTimeText = getFormattedTimeString(startTime); 316 String endTimeText = getFormattedTimeString(endTime); 317 318 mProgramTimeTextView.setText(getContext().getString( 319 R.string.channel_banner_program_time_format, startTimeText, endTimeText)); 320 321 long currTime = System.currentTimeMillis(); 322 if (currTime <= startTime) { 323 mRemainingTimeView.setProgress(0); 324 } else if (currTime >= endTime) { 325 mRemainingTimeView.setProgress(100); 326 } else { 327 mRemainingTimeView.setProgress( 328 (int) (100 *(currTime - startTime) / (endTime - startTime))); 329 } 330 } else { 331 mProgramTimeTextView.setVisibility(View.GONE); 332 mRemainingTimeView.setVisibility(View.GONE); 333 } 334 } else { 335 mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title)); 336 mProgramTimeTextView.setVisibility(View.GONE); 337 mRemainingTimeView.setVisibility(View.GONE); 338 } 339 340 updateText(mRatingTextView, Utils.contentRatingsToString(program.getContentRatings())); 341 342 if (!TextUtils.isEmpty(program.getDescription())) { 343 mProgrameDescriptionTextView.setVisibility(View.VISIBLE); 344 mProgrameDescriptionTextView.setText(program.getDescription()); 345 } else { 346 mProgrameDescriptionTextView.setVisibility(View.GONE); 347 } 348 } 349 350 private void handleNoProgramInformation() { 351 mProgramTextView.setText(getContext().getString(R.string.channel_banner_no_title)); 352 mProgramTimeTextView.setVisibility(View.GONE); 353 mRemainingTimeView.setVisibility(View.GONE); 354 mProgrameDescriptionTextView.setVisibility(View.GONE); 355 } 356 357 private int estimateLineCount(String str, Typeface font, float textSize, int width) { 358 if (width == 0) { 359 return -1; 360 } 361 Paint paint = new Paint(); 362 paint.setTypeface(font); 363 paint.setTextSize(textSize); 364 // Add +1 to measured size, because number of lines becomes 2 365 // when measured size equals width. 366 return divideRoundUp((int) paint.measureText(str) + 1, width); 367 } 368 369 private int divideRoundUp(int x, int y) { 370 return (x + y - 1) / y; 371 } 372} 373