ProgramTableAdapter.java revision 3dfa929b24f38ac7836450176d88ceab41dc6ac5
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 17package com.android.tv.guide; 18 19import static com.android.tv.util.ImageLoader.ImageLoaderCallback; 20 21import android.animation.Animator; 22import android.animation.ObjectAnimator; 23import android.animation.PropertyValuesHolder; 24import android.content.Context; 25import android.content.res.ColorStateList; 26import android.content.res.Resources; 27import android.graphics.Bitmap; 28import android.media.tv.TvContentRating; 29import android.media.tv.TvInputInfo; 30import android.os.Handler; 31import android.support.annotation.NonNull; 32import android.support.annotation.Nullable; 33import android.support.v7.widget.RecyclerView; 34import android.support.v7.widget.RecyclerView.RecycledViewPool; 35import android.text.Html; 36import android.text.Spannable; 37import android.text.SpannableString; 38import android.text.TextUtils; 39import android.text.style.TextAppearanceSpan; 40import android.util.Log; 41import android.util.TypedValue; 42import android.view.LayoutInflater; 43import android.view.View; 44import android.view.ViewGroup; 45import android.view.ViewParent; 46import android.view.ViewTreeObserver; 47import android.view.accessibility.AccessibilityManager; 48import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; 49import android.widget.ImageView; 50import android.widget.LinearLayout; 51import android.widget.TextView; 52 53import com.android.tv.R; 54import com.android.tv.TvApplication; 55import com.android.tv.common.TvCommonUtils; 56import com.android.tv.common.feature.CommonFeatures; 57import com.android.tv.data.Channel; 58import com.android.tv.data.Program; 59import com.android.tv.data.Program.CriticScore; 60import com.android.tv.dvr.DvrDataManager; 61import com.android.tv.dvr.DvrManager; 62import com.android.tv.dvr.data.ScheduledRecording; 63import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; 64import com.android.tv.parental.ParentalControlSettings; 65import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; 66import com.android.tv.util.ImageCache; 67import com.android.tv.util.ImageLoader; 68import com.android.tv.util.ImageLoader.LoadTvInputLogoTask; 69import com.android.tv.util.TvInputManagerHelper; 70import com.android.tv.util.Utils; 71 72import java.util.ArrayList; 73import java.util.List; 74 75/** 76 * Adapts the {@link ProgramListAdapter} list to the body of the program guide table. 77 */ 78public class ProgramTableAdapter extends RecyclerView.Adapter<ProgramTableAdapter.ProgramRowHolder> 79 implements ProgramManager.TableEntryChangedListener { 80 private static final String TAG = "ProgramTableAdapter"; 81 private static final boolean DEBUG = false; 82 83 private final Context mContext; 84 private final TvInputManagerHelper mTvInputManagerHelper; 85 private final DvrManager mDvrManager; 86 private final DvrDataManager mDvrDataManager; 87 private final ProgramManager mProgramManager; 88 private final AccessibilityManager mAccessibilityManager; 89 private final ProgramGuide mProgramGuide; 90 private final Handler mHandler = new Handler(); 91 private final List<ProgramListAdapter> mProgramListAdapters = new ArrayList<>(); 92 private final RecycledViewPool mRecycledViewPool; 93 // views to be be reused when displaying critic scores 94 private final List<LinearLayout> mCriticScoreViews; 95 96 private final int mChannelLogoWidth; 97 private final int mChannelLogoHeight; 98 private final int mImageWidth; 99 private final int mImageHeight; 100 private final String mProgramTitleForNoInformation; 101 private final String mProgramTitleForBlockedChannel; 102 private final int mChannelTextColor; 103 private final int mChannelBlockedTextColor; 104 private final int mDetailTextColor; 105 private final int mDetailGrayedTextColor; 106 private final int mAnimationDuration; 107 private final int mDetailPadding; 108 private final TextAppearanceSpan mEpisodeTitleStyle; 109 private final String mProgramRecordableText; 110 private final String mRecordingScheduledText; 111 private final String mRecordingConflictText; 112 private final String mRecordingFailedText; 113 private final String mRecordingInProgressText; 114 private final int mDvrPaddingStartWithTrack; 115 private final int mDvrPaddingStartWithOutTrack; 116 117 public ProgramTableAdapter(Context context, ProgramManager programManager, 118 ProgramGuide programGuide) { 119 mContext = context; 120 mAccessibilityManager = 121 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); 122 mTvInputManagerHelper = TvApplication.getSingletons(context).getTvInputManagerHelper(); 123 if (CommonFeatures.DVR.isEnabled(context)) { 124 mDvrManager = TvApplication.getSingletons(context).getDvrManager(); 125 mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); 126 } else { 127 mDvrManager = null; 128 mDvrDataManager = null; 129 } 130 mProgramManager = programManager; 131 mProgramGuide = programGuide; 132 133 Resources res = context.getResources(); 134 mChannelLogoWidth = res.getDimensionPixelSize( 135 R.dimen.program_guide_table_header_column_channel_logo_width); 136 mChannelLogoHeight = res.getDimensionPixelSize( 137 R.dimen.program_guide_table_header_column_channel_logo_height); 138 mImageWidth = res.getDimensionPixelSize( 139 R.dimen.program_guide_table_detail_image_width); 140 mImageHeight = res.getDimensionPixelSize( 141 R.dimen.program_guide_table_detail_image_height); 142 mProgramTitleForNoInformation = res.getString( 143 R.string.program_title_for_no_information); 144 mProgramTitleForBlockedChannel = res.getString( 145 R.string.program_title_for_blocked_channel); 146 mChannelTextColor = res.getColor( 147 R.color.program_guide_table_header_column_channel_number_text_color, null); 148 mChannelBlockedTextColor = res.getColor( 149 R.color.program_guide_table_header_column_channel_number_blocked_text_color, null); 150 mDetailTextColor = res.getColor( 151 R.color.program_guide_table_detail_title_text_color, null); 152 mDetailGrayedTextColor = res.getColor( 153 R.color.program_guide_table_detail_title_grayed_text_color, null); 154 mAnimationDuration = 155 res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration); 156 mDetailPadding = res.getDimensionPixelOffset( 157 R.dimen.program_guide_table_detail_padding); 158 mProgramRecordableText = res.getString(R.string.dvr_epg_program_recordable); 159 mRecordingScheduledText = res.getString(R.string.dvr_epg_program_recording_scheduled); 160 mRecordingConflictText = res.getString(R.string.dvr_epg_program_recording_conflict); 161 mRecordingFailedText = res.getString(R.string.dvr_epg_program_recording_failed); 162 mRecordingInProgressText = res.getString(R.string.dvr_epg_program_recording_in_progress); 163 mDvrPaddingStartWithTrack = res.getDimensionPixelOffset( 164 R.dimen.program_guide_table_detail_dvr_margin_start); 165 mDvrPaddingStartWithOutTrack = res.getDimensionPixelOffset( 166 R.dimen.program_guide_table_detail_dvr_margin_start_without_track); 167 168 int episodeTitleSize = res.getDimensionPixelSize( 169 R.dimen.program_guide_table_detail_episode_title_text_size); 170 ColorStateList episodeTitleColor = ColorStateList.valueOf( 171 res.getColor(R.color.program_guide_table_detail_episode_title_text_color, null)); 172 mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, 173 episodeTitleColor, null); 174 175 mCriticScoreViews = new ArrayList<>(); 176 mRecycledViewPool = new RecycledViewPool(); 177 mRecycledViewPool.setMaxRecycledViews(R.layout.program_guide_table_item, 178 context.getResources().getInteger( 179 R.integer.max_recycled_view_pool_epg_table_item)); 180 mProgramManager.addListener(new ProgramManager.ListenerAdapter() { 181 @Override 182 public void onChannelsUpdated() { 183 update(); 184 } 185 }); 186 update(); 187 mProgramManager.addTableEntryChangedListener(this); 188 } 189 190 private void update() { 191 if (DEBUG) Log.d(TAG, "update " + mProgramManager.getChannelCount() + " channels"); 192 for (TableEntriesUpdatedListener listener : mProgramListAdapters) { 193 mProgramManager.removeTableEntriesUpdatedListener(listener); 194 } 195 mProgramListAdapters.clear(); 196 for (int i = 0; i < mProgramManager.getChannelCount(); i++) { 197 ProgramListAdapter listAdapter = new ProgramListAdapter(mContext.getResources(), 198 mProgramManager, i); 199 mProgramManager.addTableEntriesUpdatedListener(listAdapter); 200 mProgramListAdapters.add(listAdapter); 201 } 202 notifyDataSetChanged(); 203 } 204 205 @Override 206 public int getItemCount() { 207 return mProgramListAdapters.size(); 208 } 209 210 @Override 211 public int getItemViewType(int position) { 212 return R.layout.program_guide_table_row; 213 } 214 215 @Override 216 public void onBindViewHolder(ProgramRowHolder holder, int position) { 217 holder.onBind(position); 218 } 219 220 @Override 221 public void onBindViewHolder(ProgramRowHolder holder, int position, List<Object> payloads) { 222 if (!payloads.isEmpty()) { 223 holder.updateDetailView(); 224 } else { 225 super.onBindViewHolder(holder, position, payloads); 226 } 227 } 228 229 @Override 230 public ProgramRowHolder onCreateViewHolder(ViewGroup parent, int viewType) { 231 View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); 232 ProgramRow programRow = (ProgramRow) itemView.findViewById(R.id.row); 233 programRow.setRecycledViewPool(mRecycledViewPool); 234 return new ProgramRowHolder(itemView); 235 } 236 237 @Override 238 public void onTableEntryChanged(ProgramManager.TableEntry tableEntry) { 239 int channelIndex = mProgramManager.getChannelIndex(tableEntry.channelId); 240 int pos = mProgramManager.getProgramIdIndex(tableEntry.channelId, tableEntry.getId()); 241 if (DEBUG) Log.d(TAG, "update(" + channelIndex + ", " + pos + ")"); 242 mProgramListAdapters.get(channelIndex).notifyItemChanged(pos, tableEntry); 243 notifyItemChanged(channelIndex, true); 244 } 245 246 // TODO: make it static 247 public class ProgramRowHolder extends RecyclerView.ViewHolder 248 implements ProgramRow.ChildFocusListener { 249 250 private final ViewGroup mContainer; 251 private final ProgramRow mProgramRow; 252 private ProgramManager.TableEntry mSelectedEntry; 253 private Animator mDetailOutAnimator; 254 private Animator mDetailInAnimator; 255 private final Runnable mDetailInStarter = new Runnable() { 256 @Override 257 public void run() { 258 mProgramRow.removeOnScrollListener(mOnScrollListener); 259 if (mDetailInAnimator != null) { 260 mDetailInAnimator.start(); 261 } 262 } 263 }; 264 265 private final RecyclerView.OnScrollListener mOnScrollListener = 266 new RecyclerView.OnScrollListener() { 267 @Override 268 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 269 onHorizontalScrolled(); 270 } 271 }; 272 273 private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener = 274 new ViewTreeObserver.OnGlobalFocusChangeListener() { 275 @Override 276 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 277 onChildFocus(isChild(oldFocus) ? oldFocus : null, 278 isChild(newFocus) ? newFocus : null); 279 } 280 }; 281 282 // Members of Program Details 283 private final ViewGroup mDetailView; 284 private final ImageView mImageView; 285 private final ImageView mBlockView; 286 private final TextView mTitleView; 287 private final TextView mTimeView; 288 private final LinearLayout mCriticScoresLayout; 289 private final TextView mDescriptionView; 290 private final TextView mAspectRatioView; 291 private final TextView mResolutionView; 292 private final ImageView mDvrIconView; 293 private final TextView mDvrTextIconView; 294 private final TextView mDvrStatusView; 295 private final ViewGroup mDvrIndicator; 296 297 // Members of Channel Header 298 private Channel mChannel; 299 private final View mChannelHeaderView; 300 private final TextView mChannelNumberView; 301 private final TextView mChannelNameView; 302 private final ImageView mChannelLogoView; 303 private final ImageView mChannelBlockView; 304 private final ImageView mInputLogoView; 305 306 private boolean mIsInputLogoVisible; 307 private AccessibilityStateChangeListener mAccessibilityStateChangeListener = 308 new AccessibilityManager.AccessibilityStateChangeListener() { 309 @Override 310 public void onAccessibilityStateChanged(boolean enable) { 311 enable &= !TvCommonUtils.isRunningInTest(); 312 mDetailView.setFocusable(enable); 313 mChannelHeaderView.setFocusable(enable); 314 } 315 }; 316 317 public ProgramRowHolder(View itemView) { 318 super(itemView); 319 320 mContainer = (ViewGroup) itemView; 321 mContainer.addOnAttachStateChangeListener( 322 new View.OnAttachStateChangeListener() { 323 @Override 324 public void onViewAttachedToWindow(View v) { 325 mContainer 326 .getViewTreeObserver() 327 .addOnGlobalFocusChangeListener(mGlobalFocusChangeListener); 328 mAccessibilityManager.addAccessibilityStateChangeListener( 329 mAccessibilityStateChangeListener); 330 } 331 332 @Override 333 public void onViewDetachedFromWindow(View v) { 334 mContainer 335 .getViewTreeObserver() 336 .removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener); 337 mAccessibilityManager.removeAccessibilityStateChangeListener( 338 mAccessibilityStateChangeListener); 339 } 340 }); 341 mProgramRow = (ProgramRow) mContainer.findViewById(R.id.row); 342 343 mDetailView = (ViewGroup) mContainer.findViewById(R.id.detail); 344 mImageView = (ImageView) mDetailView.findViewById(R.id.image); 345 mBlockView = (ImageView) mDetailView.findViewById(R.id.block); 346 mTitleView = (TextView) mDetailView.findViewById(R.id.title); 347 mTimeView = (TextView) mDetailView.findViewById(R.id.time); 348 mDescriptionView = (TextView) mDetailView.findViewById(R.id.desc); 349 mAspectRatioView = (TextView) mDetailView.findViewById(R.id.aspect_ratio); 350 mResolutionView = (TextView) mDetailView.findViewById(R.id.resolution); 351 mDvrIconView = (ImageView) mDetailView.findViewById(R.id.dvr_icon); 352 mDvrTextIconView = (TextView) mDetailView.findViewById(R.id.dvr_text_icon); 353 mDvrStatusView = (TextView) mDetailView.findViewById(R.id.dvr_status); 354 mDvrIndicator = (ViewGroup) mContainer.findViewById(R.id.dvr_indicator); 355 mCriticScoresLayout = (LinearLayout) mDetailView.findViewById(R.id.critic_scores); 356 357 mChannelHeaderView = mContainer.findViewById(R.id.header_column); 358 mChannelNumberView = (TextView) mContainer.findViewById(R.id.channel_number); 359 mChannelNameView = (TextView) mContainer.findViewById(R.id.channel_name); 360 mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo); 361 mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block); 362 mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo); 363 // TODO: Find a better way to handle talk back. 364 boolean accessibilityEnabled = mAccessibilityManager.isEnabled() 365 && !TvCommonUtils.isRunningInTest(); 366 mDetailView.setFocusable(accessibilityEnabled); 367 mChannelHeaderView.setFocusable(accessibilityEnabled); 368 } 369 370 public void onBind(int position) { 371 onBindChannel(mProgramManager.getChannel(position)); 372 373 mProgramRow.swapAdapter(mProgramListAdapters.get(position), true); 374 mProgramRow.setProgramManager(mProgramManager); 375 mProgramRow.setChannel(mProgramManager.getChannel(position)); 376 mProgramRow.setChildFocusListener(this); 377 mProgramRow.resetScroll(mProgramGuide.getTimelineRowScrollOffset()); 378 379 mDetailView.setVisibility(View.GONE); 380 381 // The bottom-left of the last channel header view will have a rounded corner. 382 mChannelHeaderView.setBackgroundResource((position < mProgramListAdapters.size() - 1) 383 ? R.drawable.program_guide_table_header_column_item_background 384 : R.drawable.program_guide_table_header_column_last_item_background); 385 } 386 387 private void onBindChannel(Channel channel) { 388 if (DEBUG) Log.d(TAG, "onBindChannel " + channel); 389 390 mChannel = channel; 391 mInputLogoView.setVisibility(View.GONE); 392 mIsInputLogoVisible = false; 393 if (channel == null) { 394 mChannelNumberView.setVisibility(View.GONE); 395 mChannelNameView.setVisibility(View.GONE); 396 mChannelLogoView.setVisibility(View.GONE); 397 mChannelBlockView.setVisibility(View.GONE); 398 return; 399 } 400 401 String displayNumber = channel.getDisplayNumber(); 402 if (displayNumber == null) { 403 mChannelNumberView.setVisibility(View.GONE); 404 } else { 405 int size; 406 if (displayNumber.length() <= 4) { 407 size = R.dimen.program_guide_table_header_column_channel_number_large_font_size; 408 } else { 409 size = R.dimen.program_guide_table_header_column_channel_number_small_font_size; 410 } 411 mChannelNumberView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 412 mChannelNumberView.getContext().getResources().getDimension(size)); 413 mChannelNumberView.setText(displayNumber); 414 mChannelNumberView.setVisibility(View.VISIBLE); 415 } 416 mChannelNumberView.setTextColor( 417 isChannelLocked(channel) ? mChannelBlockedTextColor : mChannelTextColor); 418 419 mChannelLogoView.setImageBitmap(null); 420 mChannelLogoView.setVisibility(View.GONE); 421 if (isChannelLocked(channel)) { 422 mChannelNameView.setVisibility(View.GONE); 423 mChannelBlockView.setVisibility(View.VISIBLE); 424 } else { 425 mChannelNameView.setText(channel.getDisplayName()); 426 mChannelNameView.setVisibility(View.VISIBLE); 427 mChannelBlockView.setVisibility(View.GONE); 428 429 mChannel.loadBitmap(itemView.getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, 430 mChannelLogoWidth, mChannelLogoHeight, 431 createChannelLogoLoadedCallback(this, channel.getId())); 432 } 433 } 434 435 public boolean isChild(View view) { 436 if (view == null) { 437 return false; 438 } 439 for (ViewParent p = view.getParent(); p != null; p = p.getParent()) { 440 if (p == mContainer) { 441 return true; 442 } 443 } 444 return false; 445 } 446 447 @Override 448 public void onChildFocus(View oldFocus, View newFocus) { 449 if (newFocus == null) { 450 return; 451 } 452 // When the accessibility service is enabled, focus might be put on channel's header or 453 // detail view, besides program items. 454 if (newFocus == mChannelHeaderView) { 455 mSelectedEntry = ((ProgramItemView) mProgramRow.getChildAt(0)).getTableEntry(); 456 } else if (newFocus == mDetailView) { 457 return; 458 } else { 459 mSelectedEntry = ((ProgramItemView) newFocus).getTableEntry(); 460 } 461 if (oldFocus == null) { 462 updateDetailView(); 463 return; 464 } 465 466 if (Program.isValid(mSelectedEntry.program)) { 467 Program program = mSelectedEntry.program; 468 if (getProgramBlock(program) == null) { 469 program.prefetchPosterArt(itemView.getContext(), mImageWidth, mImageHeight); 470 } 471 } 472 473 // -1 means the selection goes rightwards and 1 goes leftwards 474 int direction = oldFocus.getLeft() < newFocus.getLeft() ? -1 : 1; 475 View detailContentView = mDetailView.findViewById(R.id.detail_content); 476 477 if (mDetailInAnimator == null) { 478 mDetailOutAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView, 479 PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f), 480 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 481 0f, direction * mDetailPadding)); 482 mDetailOutAnimator.setDuration(mAnimationDuration); 483 mDetailOutAnimator.addListener( 484 new HardwareLayerAnimatorListenerAdapter(detailContentView) { 485 @Override 486 public void onAnimationEnd(Animator animator) { 487 super.onAnimationEnd(animator); 488 mDetailOutAnimator = null; 489 mHandler.removeCallbacks(mDetailInStarter); 490 mHandler.postDelayed(mDetailInStarter, mAnimationDuration); 491 } 492 }); 493 494 mProgramRow.addOnScrollListener(mOnScrollListener); 495 mDetailOutAnimator.start(); 496 } else { 497 if (mDetailInAnimator.isStarted()) { 498 mDetailInAnimator.cancel(); 499 detailContentView.setAlpha(0); 500 } 501 502 mHandler.removeCallbacks(mDetailInStarter); 503 mHandler.postDelayed(mDetailInStarter, mAnimationDuration); 504 } 505 506 mDetailInAnimator = ObjectAnimator.ofPropertyValuesHolder(detailContentView, 507 PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), 508 PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 509 direction * -mDetailPadding, 0f)); 510 mDetailInAnimator.setDuration(mAnimationDuration); 511 mDetailInAnimator.addListener( 512 new HardwareLayerAnimatorListenerAdapter(detailContentView) { 513 @Override 514 public void onAnimationStart(Animator animator) { 515 super.onAnimationStart(animator); 516 updateDetailView(); 517 } 518 519 @Override 520 public void onAnimationEnd(Animator animator) { 521 super.onAnimationEnd(animator); 522 mDetailInAnimator = null; 523 } 524 }); 525 } 526 527 private void updateDetailView() { 528 if (mSelectedEntry == null) { 529 // The view holder is never on focus before. 530 return; 531 } 532 if (DEBUG) Log.d(TAG, "updateDetailView"); 533 mCriticScoresLayout.removeAllViews(); 534 if (Program.isValid(mSelectedEntry.program)) { 535 mTitleView.setTextColor(mDetailTextColor); 536 Context context = itemView.getContext(); 537 Program program = mSelectedEntry.program; 538 539 TvContentRating blockedRating = getProgramBlock(program); 540 541 updatePosterArt(null); 542 if (blockedRating == null) { 543 program.loadPosterArt(context, mImageWidth, mImageHeight, 544 createProgramPosterArtCallback(this, program)); 545 } 546 547 String episodeTitle = program.getEpisodeDisplayTitle(mContext); 548 if (TextUtils.isEmpty(episodeTitle)) { 549 mTitleView.setText(program.getTitle()); 550 } else { 551 String title = program.getTitle(); 552 String fullTitle = title + " " + episodeTitle; 553 554 SpannableString text = new SpannableString(fullTitle); 555 text.setSpan(mEpisodeTitleStyle, 556 fullTitle.length() - episodeTitle.length(), fullTitle.length(), 557 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 558 mTitleView.setText(text); 559 } 560 561 updateTextView(mTimeView, Utils.getDurationString(context, 562 program.getStartTimeUtcMillis(), 563 program.getEndTimeUtcMillis(), false)); 564 565 boolean trackMetaDataVisible = false; 566 trackMetaDataVisible |= 567 updateTextView(mAspectRatioView, Utils.getAspectRatioString( 568 program.getVideoWidth(), program.getVideoHeight())); 569 570 int videoDefinitionLevel = Utils.getVideoDefinitionLevelFromSize( 571 program.getVideoWidth(), program.getVideoHeight()); 572 trackMetaDataVisible |= 573 updateTextView(mResolutionView, Utils.getVideoDefinitionLevelString( 574 context, videoDefinitionLevel)); 575 576 if (mDvrManager != null && mDvrManager.isProgramRecordable(program)) { 577 ScheduledRecording scheduledRecording = 578 mDvrDataManager.getScheduledRecordingForProgramId(program.getId()); 579 String statusText = mProgramRecordableText; 580 int iconResId = 0; 581 if (scheduledRecording != null) { 582 if (mDvrManager.isConflicting(scheduledRecording)) { 583 iconResId = R.drawable.ic_warning_white_12dp; 584 statusText = mRecordingConflictText; 585 } else { 586 switch (scheduledRecording.getState()) { 587 case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: 588 iconResId = R.drawable.ic_recording_program; 589 statusText = mRecordingInProgressText; 590 break; 591 case ScheduledRecording.STATE_RECORDING_NOT_STARTED: 592 iconResId = R.drawable.ic_scheduled_white; 593 statusText = mRecordingScheduledText; 594 break; 595 case ScheduledRecording.STATE_RECORDING_FAILED: 596 iconResId = R.drawable.ic_warning_white_12dp; 597 statusText = mRecordingFailedText; 598 break; 599 default: 600 iconResId = 0; 601 } 602 } 603 } 604 if (iconResId == 0) { 605 mDvrIconView.setVisibility(View.GONE); 606 mDvrTextIconView.setVisibility(View.VISIBLE); 607 } else { 608 mDvrTextIconView.setVisibility(View.GONE); 609 mDvrIconView.setImageResource(iconResId); 610 mDvrIconView.setVisibility(View.VISIBLE); 611 } 612 if (!trackMetaDataVisible) { 613 mDvrIndicator.setPaddingRelative(mDvrPaddingStartWithOutTrack, 0, 0, 0); 614 } else { 615 mDvrIndicator.setPaddingRelative(mDvrPaddingStartWithTrack, 0, 0, 0); 616 } 617 mDvrIndicator.setVisibility(View.VISIBLE); 618 mDvrStatusView.setText(statusText); 619 } else { 620 mDvrIndicator.setVisibility(View.GONE); 621 } 622 623 624 if (blockedRating == null) { 625 mBlockView.setVisibility(View.GONE); 626 updateTextView(mDescriptionView, program.getDescription()); 627 } else { 628 mBlockView.setVisibility(View.VISIBLE); 629 updateTextView(mDescriptionView, getBlockedDescription(blockedRating)); 630 } 631 } else { 632 mTitleView.setTextColor(mDetailGrayedTextColor); 633 if (mSelectedEntry.isBlocked()) { 634 updateTextView(mTitleView, mProgramTitleForBlockedChannel); 635 } else { 636 updateTextView(mTitleView, mProgramTitleForNoInformation); 637 } 638 mImageView.setVisibility(View.GONE); 639 mBlockView.setVisibility(View.GONE); 640 mTimeView.setVisibility(View.GONE); 641 mDvrIndicator.setVisibility(View.GONE); 642 mDescriptionView.setVisibility(View.GONE); 643 mAspectRatioView.setVisibility(View.GONE); 644 mResolutionView.setVisibility(View.GONE); 645 } 646 } 647 648 private TvContentRating getProgramBlock(Program program) { 649 ParentalControlSettings parental = mTvInputManagerHelper.getParentalControlSettings(); 650 if (!parental.isParentalControlsEnabled()) { 651 return null; 652 } 653 return parental.getBlockedRating(program.getContentRatings()); 654 } 655 656 private boolean isChannelLocked(Channel channel) { 657 return mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled() 658 && channel.isLocked(); 659 } 660 661 private String getBlockedDescription(TvContentRating blockedRating) { 662 String name = mTvInputManagerHelper.getContentRatingsManager() 663 .getDisplayNameForRating(blockedRating); 664 if (TextUtils.isEmpty(name)) { 665 return mContext.getString(R.string.program_guide_content_locked); 666 } else { 667 return mContext.getString(R.string.program_guide_content_locked_format, name); 668 } 669 } 670 671 /** 672 * Update tv input logo. It should be called when the visible child item in ProgramGrid 673 * changed. 674 */ 675 public void updateInputLogo(int lastPosition, boolean forceShow) { 676 if (mChannel == null) { 677 mInputLogoView.setVisibility(View.GONE); 678 mIsInputLogoVisible = false; 679 return; 680 } 681 682 boolean showLogo = forceShow; 683 if (!showLogo) { 684 Channel lastChannel = mProgramManager.getChannel(lastPosition); 685 if (lastChannel == null 686 || !mChannel.getInputId().equals(lastChannel.getInputId())) { 687 showLogo = true; 688 } 689 } 690 691 if (showLogo) { 692 if (!mIsInputLogoVisible) { 693 mIsInputLogoVisible = true; 694 TvInputInfo info = mTvInputManagerHelper.getTvInputInfo(mChannel.getInputId()); 695 if (info != null) { 696 LoadTvInputLogoTask task = new LoadTvInputLogoTask( 697 itemView.getContext(), ImageCache.getInstance(), info); 698 ImageLoader.loadBitmap(createTvInputLogoLoadedCallback(info, this), task); 699 } 700 } 701 } else { 702 mInputLogoView.setVisibility(View.GONE); 703 mInputLogoView.setImageDrawable(null); 704 mIsInputLogoVisible = false; 705 } 706 } 707 708 // The return value of this method will indicate the target view is visible (true) 709 // or gone (false). 710 private boolean updateTextView(TextView textView, String text) { 711 if (!TextUtils.isEmpty(text)) { 712 textView.setVisibility(View.VISIBLE); 713 textView.setText(text); 714 return true; 715 } else { 716 textView.setVisibility(View.GONE); 717 return false; 718 } 719 } 720 721 private void updatePosterArt(@Nullable Bitmap posterArt) { 722 mImageView.setImageBitmap(posterArt); 723 mImageView.setVisibility(posterArt == null ? View.GONE : View.VISIBLE); 724 } 725 726 private void updateChannelLogo(@Nullable Bitmap logo) { 727 mChannelLogoView.setImageBitmap(logo); 728 mChannelNameView.setVisibility(View.GONE); 729 mChannelLogoView.setVisibility(View.VISIBLE); 730 } 731 732 private void updateInputLogoInternal(@NonNull Bitmap tvInputLogo) { 733 if (!mIsInputLogoVisible) { 734 return; 735 } 736 mInputLogoView.setImageBitmap(tvInputLogo); 737 mInputLogoView.setVisibility(View.VISIBLE); 738 } 739 740 private void updateCriticScoreView(ProgramRowHolder holder, final long programId, 741 CriticScore criticScore, View view) { 742 TextView criticScoreSource = (TextView) view.findViewById(R.id.critic_score_source); 743 TextView criticScoreText = (TextView) view.findViewById(R.id.critic_score_score); 744 ImageView criticScoreLogo = (ImageView) view.findViewById(R.id.critic_score_logo); 745 746 //set the appropriate information in the views 747 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { 748 criticScoreSource.setText(Html.fromHtml(criticScore.source, 749 Html.FROM_HTML_MODE_LEGACY)); 750 } else { 751 criticScoreSource.setText(Html.fromHtml(criticScore.source)); 752 } 753 criticScoreText.setText(criticScore.score); 754 criticScoreSource.setVisibility(View.VISIBLE); 755 criticScoreText.setVisibility(View.VISIBLE); 756 ImageLoader.loadBitmap(mContext, criticScore.logoUrl, 757 createCriticScoreLogoCallback(holder, programId, criticScoreLogo)); 758 } 759 760 private void onHorizontalScrolled() { 761 if (mDetailInAnimator != null) { 762 mHandler.removeCallbacks(mDetailInStarter); 763 mHandler.postDelayed(mDetailInStarter, mAnimationDuration); 764 } 765 } 766 } 767 768 private static ImageLoaderCallback<ProgramRowHolder> createCriticScoreLogoCallback( 769 ProgramRowHolder holder, final long programId, ImageView logoView) { 770 return new ImageLoaderCallback<ProgramRowHolder>(holder) { 771 @Override 772 public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logoImage) { 773 if (logoImage == null || holder.mSelectedEntry == null 774 || holder.mSelectedEntry.program == null 775 || holder.mSelectedEntry.program.getId() != programId) { 776 logoView.setVisibility(View.GONE); 777 } else { 778 logoView.setImageBitmap(logoImage); 779 logoView.setVisibility(View.VISIBLE); 780 } 781 } 782 }; 783 } 784 785 private static ImageLoaderCallback<ProgramRowHolder> createProgramPosterArtCallback( 786 ProgramRowHolder holder, final Program program) { 787 return new ImageLoaderCallback<ProgramRowHolder>(holder) { 788 @Override 789 public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap posterArt) { 790 if (posterArt == null || holder.mSelectedEntry == null 791 || holder.mSelectedEntry.program == null) { 792 return; 793 } 794 String posterArtUri = holder.mSelectedEntry.program.getPosterArtUri(); 795 if (posterArtUri == null || !posterArtUri.equals(program.getPosterArtUri())) { 796 return; 797 } 798 holder.updatePosterArt(posterArt); 799 } 800 }; 801 } 802 803 private static ImageLoaderCallback<ProgramRowHolder> createChannelLogoLoadedCallback( 804 ProgramRowHolder holder, final long channelId) { 805 return new ImageLoaderCallback<ProgramRowHolder>(holder) { 806 @Override 807 public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logo) { 808 if (logo == null || holder.mChannel == null 809 || holder.mChannel.getId() != channelId) { 810 return; 811 } 812 holder.updateChannelLogo(logo); 813 } 814 }; 815 } 816 817 private static ImageLoaderCallback<ProgramRowHolder> createTvInputLogoLoadedCallback( 818 final TvInputInfo info, ProgramRowHolder holder) { 819 return new ImageLoaderCallback<ProgramRowHolder>(holder) { 820 @Override 821 public void onBitmapLoaded(ProgramRowHolder holder, @Nullable Bitmap logo) { 822 if (logo != null && holder.mChannel != null && info.getId() 823 .equals(holder.mChannel.getInputId())) { 824 holder.updateInputLogoInternal(logo); 825 } 826 } 827 }; 828 } 829} 830