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