/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.guide; import android.animation.Animator; import android.animation.ObjectAnimator; import android.animation.PropertyValuesHolder; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Bitmap; import android.media.tv.TvContentRating; import android.media.tv.TvInputInfo; import android.os.Handler; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.RecycledViewPool; import android.text.Html; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.style.TextAppearanceSpan; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import com.android.tv.R; import com.android.tv.TvSingletons; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.common.util.CommonUtils; import com.android.tv.data.api.Channel; import com.android.tv.data.api.Program; import com.android.tv.data.api.Program.CriticScore; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrManager; import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.guide.ProgramManager.TableEntriesUpdatedListener; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.ui.HardwareLayerAnimatorListenerAdapter; import com.android.tv.util.TvInputManagerHelper; import com.android.tv.util.Utils; import com.android.tv.util.images.ImageCache; import com.android.tv.util.images.ImageLoader; import com.android.tv.util.images.ImageLoader.ImageLoaderCallback; import com.android.tv.util.images.ImageLoader.LoadTvInputLogoTask; import com.android.tv.common.flags.UiFlags; import java.util.ArrayList; import java.util.List; /** Adapts the {@link ProgramListAdapter} list to the body of the program guide table. */ class ProgramTableAdapter extends RecyclerView.Adapter implements ProgramManager.TableEntryChangedListener { private static final String TAG = "ProgramTableAdapter"; private static final boolean DEBUG = false; private final Context mContext; private final TvInputManagerHelper mTvInputManagerHelper; private final DvrManager mDvrManager; private final DvrDataManager mDvrDataManager; private final ProgramManager mProgramManager; private final AccessibilityManager mAccessibilityManager; private final ProgramGuide mProgramGuide; private final Handler mHandler = new Handler(); private final List mProgramListAdapters = new ArrayList<>(); private final RecycledViewPool mRecycledViewPool; // views to be be reused when displaying critic scores private final List mCriticScoreViews; private final int mChannelLogoWidth; private final int mChannelLogoHeight; private final int mImageWidth; private final int mImageHeight; private final String mProgramTitleForNoInformation; private final String mProgramTitleForBlockedChannel; private final int mChannelTextColor; private final int mChannelBlockedTextColor; private final int mDetailTextColor; private final int mDetailGrayedTextColor; private final int mAnimationDuration; private final int mDetailPadding; private final TextAppearanceSpan mEpisodeTitleStyle; private final String mProgramRecordableText; private final String mRecordingScheduledText; private final String mRecordingConflictText; private final String mRecordingFailedText; private final String mRecordingInProgressText; private final int mDvrPaddingStartWithTrack; private final int mDvrPaddingStartWithOutTrack; private final UiFlags mUiFlags; private RecyclerView mRecyclerView; ProgramTableAdapter(Context context, ProgramGuide programGuide, UiFlags uiFlags) { mContext = context; mAccessibilityManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); mTvInputManagerHelper = TvSingletons.getSingletons(context).getTvInputManagerHelper(); if (CommonFeatures.DVR.isEnabled(context)) { mDvrManager = TvSingletons.getSingletons(context).getDvrManager(); mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager(); } else { mDvrManager = null; mDvrDataManager = null; } mProgramGuide = programGuide; mProgramManager = programGuide.getProgramManager(); mUiFlags = uiFlags; Resources res = context.getResources(); mChannelLogoWidth = res.getDimensionPixelSize( R.dimen.program_guide_table_header_column_channel_logo_width); mChannelLogoHeight = res.getDimensionPixelSize( R.dimen.program_guide_table_header_column_channel_logo_height); mImageWidth = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_image_width); mImageHeight = res.getDimensionPixelSize(R.dimen.program_guide_table_detail_image_height); mProgramTitleForNoInformation = res.getString(R.string.program_title_for_no_information); mProgramTitleForBlockedChannel = res.getString(R.string.program_title_for_blocked_channel); mChannelTextColor = res.getColor( R.color.program_guide_table_header_column_channel_number_text_color, null); mChannelBlockedTextColor = res.getColor( R.color.program_guide_table_header_column_channel_number_blocked_text_color, null); mDetailTextColor = res.getColor(R.color.program_guide_table_detail_title_text_color, null); mDetailGrayedTextColor = res.getColor(R.color.program_guide_table_detail_title_grayed_text_color, null); mAnimationDuration = res.getInteger(R.integer.program_guide_table_detail_fade_anim_duration); mDetailPadding = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_padding); mProgramRecordableText = res.getString(R.string.dvr_epg_program_recordable); mRecordingScheduledText = res.getString(R.string.dvr_epg_program_recording_scheduled); mRecordingConflictText = res.getString(R.string.dvr_epg_program_recording_conflict); mRecordingFailedText = res.getString(R.string.dvr_epg_program_recording_failed); mRecordingInProgressText = res.getString(R.string.dvr_epg_program_recording_in_progress); mDvrPaddingStartWithTrack = res.getDimensionPixelOffset(R.dimen.program_guide_table_detail_dvr_margin_start); mDvrPaddingStartWithOutTrack = res.getDimensionPixelOffset( R.dimen.program_guide_table_detail_dvr_margin_start_without_track); int episodeTitleSize = res.getDimensionPixelSize( R.dimen.program_guide_table_detail_episode_title_text_size); ColorStateList episodeTitleColor = ColorStateList.valueOf( res.getColor( R.color.program_guide_table_detail_episode_title_text_color, null)); mEpisodeTitleStyle = new TextAppearanceSpan(null, 0, episodeTitleSize, episodeTitleColor, null); mCriticScoreViews = new ArrayList<>(); mRecycledViewPool = new RecycledViewPool(); mRecycledViewPool.setMaxRecycledViews( R.layout.program_guide_table_item, context.getResources().getInteger(R.integer.max_recycled_view_pool_epg_table_item)); mProgramManager.addListener( new ProgramManager.ListenerAdapter() { @Override public void onChannelsUpdated() { update(); } }); update(); mProgramManager.addTableEntryChangedListener(this); } private void update() { if (DEBUG) Log.d(TAG, "update " + mProgramManager.getChannelCount() + " channels"); for (TableEntriesUpdatedListener listener : mProgramListAdapters) { mProgramManager.removeTableEntriesUpdatedListener(listener); } mProgramListAdapters.clear(); for (int i = 0; i < mProgramManager.getChannelCount(); i++) { ProgramListAdapter listAdapter = new ProgramListAdapter(mContext.getResources(), mProgramGuide, i); mProgramManager.addTableEntriesUpdatedListener(listAdapter); mProgramListAdapters.add(listAdapter); } if (mRecyclerView != null && mRecyclerView.isComputingLayout()) { // it means that RecyclerView is in a lockdown state and any attempt to update adapter // contents will result in an exception because adapter contents cannot be changed while // RecyclerView is trying to compute the layout // postpone the change using a Handler mHandler.post(this::notifyDataSetChanged); } else { notifyDataSetChanged(); } } @Override public int getItemCount() { return mProgramListAdapters.size(); } @Override public int getItemViewType(int position) { return R.layout.program_guide_table_row; } @Override public void onBindViewHolder(ProgramRowViewHolder holder, int position) { holder.onBind(position); } @Override public void onBindViewHolder(ProgramRowViewHolder holder, int position, List payloads) { if (!payloads.isEmpty()) { holder.updateDetailView(); } else { super.onBindViewHolder(holder, position, payloads); } } @Override public ProgramRowViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(viewType, parent, false); ProgramRow programRow = (ProgramRow) itemView.findViewById(R.id.row); programRow.setRecycledViewPool(mRecycledViewPool); return new ProgramRowViewHolder(itemView); } @Override public void onTableEntryChanged(ProgramManager.TableEntry tableEntry) { int channelIndex = mProgramManager.getChannelIndex(tableEntry.channelId); int pos = mProgramManager.getProgramIdIndex(tableEntry.channelId, tableEntry.getId()); if (DEBUG) Log.d(TAG, "update(" + channelIndex + ", " + pos + ")"); if (channelIndex >= 0 && channelIndex < mProgramListAdapters.size()) { mProgramListAdapters.get(channelIndex).notifyItemChanged(pos, tableEntry); notifyItemChanged(channelIndex, true); } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { mRecyclerView = recyclerView; super.onAttachedToRecyclerView(recyclerView); } @Override public void onDetachedFromRecyclerView(RecyclerView recyclerView) { super.onDetachedFromRecyclerView(recyclerView); mRecyclerView = null; } class ProgramRowViewHolder extends RecyclerView.ViewHolder implements ProgramRow.ChildFocusListener { private final ViewGroup mContainer; private final ProgramRow mProgramRow; private ProgramManager.TableEntry mSelectedEntry; private Animator mDetailOutAnimator; private Animator mDetailInAnimator; private final Runnable mDetailInStarter = new Runnable() { @Override public void run() { mProgramRow.removeOnScrollListener(mOnScrollListener); if (mDetailInAnimator != null) { mDetailInAnimator.start(); } } }; private final Runnable mUpdateDetailViewRunnable = this::updateDetailView; private final RecyclerView.OnScrollListener mOnScrollListener = new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { onHorizontalScrolled(); } }; private final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener = new ViewTreeObserver.OnGlobalFocusChangeListener() { @Override public void onGlobalFocusChanged(View oldFocus, View newFocus) { onChildFocus( GuideUtils.isDescendant(mContainer, oldFocus) ? oldFocus : null, GuideUtils.isDescendant(mContainer, newFocus) ? newFocus : null); } }; // Members of Program Details private final ViewGroup mDetailView; private final ImageView mImageView; private final ImageView mBlockView; private final TextView mTitleView; private final TextView mTimeView; private final LinearLayout mCriticScoresLayout; private final TextView mDescriptionView; private final TextView mAspectRatioView; private final TextView mResolutionView; private final ImageView mDvrIconView; private final TextView mDvrTextIconView; private final TextView mDvrStatusView; private final ViewGroup mDvrIndicator; // Members of Channel Header private Channel mChannel; private final View mChannelHeaderView; private final TextView mChannelNumberView; private final TextView mChannelNameView; private final ImageView mChannelLogoView; private final ImageView mChannelBlockView; private final ImageView mInputLogoView; private boolean mIsInputLogoVisible; private AccessibilityStateChangeListener mAccessibilityStateChangeListener = new AccessibilityManager.AccessibilityStateChangeListener() { @Override public void onAccessibilityStateChanged(boolean enable) { enable &= !CommonUtils.isRunningInTest(); mChannelHeaderView.setFocusable(enable); } }; ProgramRowViewHolder(View itemView) { super(itemView); mContainer = (ViewGroup) itemView; mContainer.addOnAttachStateChangeListener( new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { mContainer .getViewTreeObserver() .addOnGlobalFocusChangeListener(mGlobalFocusChangeListener); mAccessibilityManager.addAccessibilityStateChangeListener( mAccessibilityStateChangeListener); } @Override public void onViewDetachedFromWindow(View v) { mContainer .getViewTreeObserver() .removeOnGlobalFocusChangeListener(mGlobalFocusChangeListener); mAccessibilityManager.removeAccessibilityStateChangeListener( mAccessibilityStateChangeListener); } }); mProgramRow = (ProgramRow) mContainer.findViewById(R.id.row); mDetailView = (ViewGroup) mContainer.findViewById(R.id.detail); mImageView = (ImageView) mDetailView.findViewById(R.id.image); mBlockView = (ImageView) mDetailView.findViewById(R.id.block); mTitleView = (TextView) mDetailView.findViewById(R.id.title); mTimeView = (TextView) mDetailView.findViewById(R.id.time); mDescriptionView = (TextView) mDetailView.findViewById(R.id.desc); mAspectRatioView = (TextView) mDetailView.findViewById(R.id.aspect_ratio); mResolutionView = (TextView) mDetailView.findViewById(R.id.resolution); mDvrIconView = (ImageView) mDetailView.findViewById(R.id.dvr_icon); mDvrTextIconView = (TextView) mDetailView.findViewById(R.id.dvr_text_icon); mDvrStatusView = (TextView) mDetailView.findViewById(R.id.dvr_status); mDvrIndicator = (ViewGroup) mContainer.findViewById(R.id.dvr_indicator); mCriticScoresLayout = (LinearLayout) mDetailView.findViewById(R.id.critic_scores); mChannelHeaderView = mContainer.findViewById(R.id.header_column); mChannelNumberView = (TextView) mContainer.findViewById(R.id.channel_number); mChannelNameView = (TextView) mContainer.findViewById(R.id.channel_name); mChannelLogoView = (ImageView) mContainer.findViewById(R.id.channel_logo); mChannelBlockView = (ImageView) mContainer.findViewById(R.id.channel_block); mInputLogoView = (ImageView) mContainer.findViewById(R.id.input_logo); boolean accessibilityEnabled = mAccessibilityManager.isEnabled() && !CommonUtils.isRunningInTest(); mChannelHeaderView.setFocusable(accessibilityEnabled); } public void onBind(int position) { onBindChannel(mProgramManager.getChannel(position)); mProgramRow.swapAdapter(mProgramListAdapters.get(position), true); mProgramRow.setProgramGuide(mProgramGuide); mProgramRow.setChannel(mProgramManager.getChannel(position)); mProgramRow.setChildFocusListener(this); mProgramRow.resetScroll(mProgramGuide.getTimelineRowScrollOffset()); mDetailView.setVisibility(View.GONE); // The bottom-left of the last channel header view will have a rounded corner. mChannelHeaderView.setBackgroundResource( (position < mProgramListAdapters.size() - 1) ? R.drawable.program_guide_table_header_column_item_background : R.drawable.program_guide_table_header_column_last_item_background); } private void onBindChannel(Channel channel) { if (DEBUG) Log.d(TAG, "onBindChannel " + channel); mChannel = channel; mInputLogoView.setVisibility(View.GONE); mIsInputLogoVisible = false; if (channel == null) { mChannelNumberView.setVisibility(View.GONE); mChannelNameView.setVisibility(View.GONE); mChannelLogoView.setVisibility(View.GONE); mChannelBlockView.setVisibility(View.GONE); return; } String displayNumber = channel.getDisplayNumber(); if (displayNumber == null) { mChannelNumberView.setVisibility(View.GONE); } else { int size; if (displayNumber.length() <= 4) { size = R.dimen.program_guide_table_header_column_channel_number_large_font_size; } else { size = R.dimen.program_guide_table_header_column_channel_number_small_font_size; } mChannelNumberView.setTextSize( TypedValue.COMPLEX_UNIT_PX, mChannelNumberView.getContext().getResources().getDimension(size)); mChannelNumberView.setText(displayNumber); mChannelNumberView.setVisibility(View.VISIBLE); } boolean isChannelLocked = isChannelLocked(channel); mChannelNumberView.setTextColor( isChannelLocked ? mChannelBlockedTextColor : mChannelTextColor); mChannelLogoView.setImageBitmap(null); mChannelLogoView.setVisibility(View.GONE); if (isChannelLocked) { mChannelNameView.setVisibility(View.GONE); mChannelBlockView.setVisibility(View.VISIBLE); } else { mChannelNameView.setText(channel.getDisplayName()); mChannelNameView.setVisibility(View.VISIBLE); mChannelBlockView.setVisibility(View.GONE); mChannel.loadBitmap( itemView.getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO, mChannelLogoWidth, mChannelLogoHeight, createChannelLogoLoadedCallback(this, channel.getId())); } } @Override public void onChildFocus(View oldFocus, View newFocus) { if (newFocus == null) { return; } // When the accessibility service is enabled, focus might be put on channel's header // or // detail view, besides program items. if (newFocus == mChannelHeaderView) { mSelectedEntry = ((ProgramItemView) mProgramRow.getChildAt(0)).getTableEntry(); } else if (newFocus == mDetailView) { return; } else { mSelectedEntry = ((ProgramItemView) newFocus).getTableEntry(); } if (oldFocus == null) { // Focus moved from other row. if (mProgramGuide.getProgramGrid().isInLayout()) { // We need to post runnable to avoid updating detail view when // the recycler view is in layout, which may cause detail view not // laid out according to the updated contents. mHandler.post(mUpdateDetailViewRunnable); } else { updateDetailView(); } return; } if (Program.isProgramValid(mSelectedEntry.program)) { Program program = mSelectedEntry.program; if (getProgramBlock(program) == null) { program.prefetchPosterArt(itemView.getContext(), mImageWidth, mImageHeight); } } // -1 means the selection goes rightwards and 1 goes leftwards int direction = oldFocus.getLeft() < newFocus.getLeft() ? -1 : 1; View detailContentView = mDetailView.findViewById(R.id.detail_content); if (mDetailInAnimator == null) { mDetailOutAnimator = ObjectAnimator.ofPropertyValuesHolder( detailContentView, PropertyValuesHolder.ofFloat(View.ALPHA, 1f, 0f), PropertyValuesHolder.ofFloat( View.TRANSLATION_X, 0f, direction * mDetailPadding)); mDetailOutAnimator.setDuration(mAnimationDuration); mDetailOutAnimator.addListener( new HardwareLayerAnimatorListenerAdapter(detailContentView) { @Override public void onAnimationEnd(Animator animator) { super.onAnimationEnd(animator); mDetailOutAnimator = null; mHandler.removeCallbacks(mDetailInStarter); mHandler.postDelayed(mDetailInStarter, mAnimationDuration); } }); mProgramRow.addOnScrollListener(mOnScrollListener); mDetailOutAnimator.start(); } else { if (mDetailInAnimator.isStarted()) { mDetailInAnimator.cancel(); detailContentView.setAlpha(0); } mHandler.removeCallbacks(mDetailInStarter); mHandler.postDelayed(mDetailInStarter, mAnimationDuration); } mDetailInAnimator = ObjectAnimator.ofPropertyValuesHolder( detailContentView, PropertyValuesHolder.ofFloat(View.ALPHA, 0f, 1f), PropertyValuesHolder.ofFloat( View.TRANSLATION_X, direction * -mDetailPadding, 0f)); mDetailInAnimator.setDuration(mAnimationDuration); mDetailInAnimator.addListener( new HardwareLayerAnimatorListenerAdapter(detailContentView) { @Override public void onAnimationStart(Animator animator) { super.onAnimationStart(animator); updateDetailView(); } @Override public void onAnimationEnd(Animator animator) { super.onAnimationEnd(animator); mDetailInAnimator = null; } }); } private void updateDetailView() { if (mSelectedEntry == null) { // The view holder is never on focus before. return; } if (DEBUG) Log.d(TAG, "updateDetailView"); mCriticScoresLayout.removeAllViews(); if (Program.isProgramValid(mSelectedEntry.program)) { mTitleView.setTextColor(mDetailTextColor); Context context = itemView.getContext(); Program program = mSelectedEntry.program; TvContentRating blockedRating = getProgramBlock(program); updatePosterArt(null); if (blockedRating == null) { program.loadPosterArt( context, mImageWidth, mImageHeight, createProgramPosterArtCallback(this, program)); } String episodeTitle = program.getEpisodeDisplayTitle(mContext); if (TextUtils.isEmpty(episodeTitle)) { mTitleView.setText(program.getTitle()); } else { String title = program.getTitle(); String fullTitle = title + " " + episodeTitle; SpannableString text = new SpannableString(fullTitle); text.setSpan( mEpisodeTitleStyle, fullTitle.length() - episodeTitle.length(), fullTitle.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); mTitleView.setText(text); } updateTextView(mTimeView, program.getDurationString(context)); boolean trackMetaDataVisible = updateTextView( mAspectRatioView, Utils.getAspectRatioString( program.getVideoWidth(), program.getVideoHeight())); int videoDefinitionLevel = Utils.getVideoDefinitionLevelFromSize( program.getVideoWidth(), program.getVideoHeight()); trackMetaDataVisible |= updateTextView( mResolutionView, Utils.getVideoDefinitionLevelString(context, videoDefinitionLevel)); if (mDvrManager != null && mDvrManager.isProgramRecordable(program)) { ScheduledRecording scheduledRecording = mDvrDataManager.getScheduledRecordingForProgramId(program.getId()); String statusText = mProgramRecordableText; int iconResId = 0; if (scheduledRecording != null) { if (mDvrManager.isConflicting(scheduledRecording)) { iconResId = R.drawable.ic_warning_white_12dp; statusText = mRecordingConflictText; } else { switch (scheduledRecording.getState()) { case ScheduledRecording.STATE_RECORDING_IN_PROGRESS: iconResId = R.drawable.ic_recording_program; statusText = mRecordingInProgressText; break; case ScheduledRecording.STATE_RECORDING_NOT_STARTED: iconResId = R.drawable.ic_scheduled_white; statusText = mRecordingScheduledText; break; case ScheduledRecording.STATE_RECORDING_FAILED: iconResId = R.drawable.ic_warning_white_12dp; statusText = mRecordingFailedText; break; default: iconResId = 0; } } } if (iconResId == 0) { mDvrIconView.setVisibility(View.GONE); mDvrTextIconView.setVisibility(View.VISIBLE); } else { mDvrTextIconView.setVisibility(View.GONE); mDvrIconView.setImageResource(iconResId); mDvrIconView.setVisibility(View.VISIBLE); } if (!trackMetaDataVisible) { mDvrIndicator.setPaddingRelative(mDvrPaddingStartWithOutTrack, 0, 0, 0); } else { mDvrIndicator.setPaddingRelative(mDvrPaddingStartWithTrack, 0, 0, 0); } mDvrIndicator.setVisibility(View.VISIBLE); mDvrStatusView.setText(statusText); } else { mDvrIndicator.setVisibility(View.GONE); } if (mUiFlags.enableCriticRatings()) { // display critic scores if any exist List criticScores = program.getCriticScores(); if (criticScores != null) { // inflate more critic score views if required if (criticScores.size() > mCriticScoreViews.size()) { LayoutInflater inflater = LayoutInflater.from(mContext); LinearLayout layout = (LinearLayout) inflater.inflate( R.layout.program_guide_critic_score_layout, null); mCriticScoreViews.add(layout); } // fill critic score views and add to layout for (int i = 0; i < criticScores.size(); i++) { View criticScoreView = mCriticScoreViews.get(i); ViewParent previousParentView = criticScoreView.getParent(); if (previousParentView != null && previousParentView instanceof ViewGroup) { ((ViewGroup) previousParentView).removeView(criticScoreView); } updateCriticScoreView( this, program.getId(), criticScores.get(i), criticScoreView); mCriticScoresLayout.addView(mCriticScoreViews.get(i)); } } } if (blockedRating == null) { mBlockView.setVisibility(View.GONE); updateTextView(mDescriptionView, program.getDescription()); } else { mBlockView.setVisibility(View.VISIBLE); updateTextView(mDescriptionView, getBlockedDescription(blockedRating)); } } else { mTitleView.setTextColor(mDetailGrayedTextColor); if (mSelectedEntry.isBlocked()) { updateTextView(mTitleView, mProgramTitleForBlockedChannel); } else { updateTextView(mTitleView, mProgramTitleForNoInformation); } mImageView.setVisibility(View.GONE); mBlockView.setVisibility(View.GONE); mTimeView.setVisibility(View.GONE); mDvrIndicator.setVisibility(View.GONE); mDescriptionView.setVisibility(View.GONE); mAspectRatioView.setVisibility(View.GONE); mResolutionView.setVisibility(View.GONE); } } private TvContentRating getProgramBlock(Program program) { ParentalControlSettings parental = mTvInputManagerHelper.getParentalControlSettings(); if (!parental.isParentalControlsEnabled()) { return null; } return parental.getBlockedRating(program.getContentRatings()); } private boolean isChannelLocked(Channel channel) { return mTvInputManagerHelper.getParentalControlSettings().isParentalControlsEnabled() && channel.isLocked(); } private String getBlockedDescription(TvContentRating blockedRating) { String name = mTvInputManagerHelper .getContentRatingsManager() .getDisplayNameForRating(blockedRating); if (TextUtils.isEmpty(name)) { return mContext.getString(R.string.program_guide_content_locked); } else { return TvContentRating.UNRATED.equals(blockedRating) ? mContext.getString(R.string.program_guide_content_locked_unrated) : mContext.getString(R.string.program_guide_content_locked_format, name); } } /** * Update tv input logo. It should be called when the visible child item in ProgramGrid * changed. */ void updateInputLogo(int lastPosition, boolean forceShow) { if (mChannel == null) { mInputLogoView.setVisibility(View.GONE); mIsInputLogoVisible = false; return; } boolean showLogo = forceShow; if (!showLogo) { Channel lastChannel = mProgramManager.getChannel(lastPosition); if (lastChannel == null || !mChannel.getInputId().equals(lastChannel.getInputId())) { showLogo = true; } } if (showLogo) { if (!mIsInputLogoVisible) { mIsInputLogoVisible = true; TvInputInfo info = mTvInputManagerHelper.getTvInputInfo(mChannel.getInputId()); if (info != null) { LoadTvInputLogoTask task = new LoadTvInputLogoTask( itemView.getContext(), ImageCache.getInstance(), info); ImageLoader.loadBitmap(createTvInputLogoLoadedCallback(info, this), task); } } } else { mInputLogoView.setVisibility(View.GONE); mInputLogoView.setImageDrawable(null); mIsInputLogoVisible = false; } } // The return value of this method will indicate the target view is visible (true) // or gone (false). private boolean updateTextView(TextView textView, String text) { if (!TextUtils.isEmpty(text)) { textView.setVisibility(View.VISIBLE); textView.setText(text); return true; } else { textView.setVisibility(View.GONE); return false; } } private void updatePosterArt(@Nullable Bitmap posterArt) { mImageView.setImageBitmap(posterArt); mImageView.setVisibility(posterArt == null ? View.GONE : View.VISIBLE); } private void updateChannelLogo(@Nullable Bitmap logo) { mChannelLogoView.setImageBitmap(logo); mChannelNameView.setVisibility(View.GONE); mChannelLogoView.setVisibility(View.VISIBLE); } private void updateInputLogoInternal(@NonNull Bitmap tvInputLogo) { if (!mIsInputLogoVisible) { return; } mInputLogoView.setImageBitmap(tvInputLogo); mInputLogoView.setVisibility(View.VISIBLE); } private void updateCriticScoreView( ProgramRowViewHolder holder, final long programId, CriticScore criticScore, View view) { TextView criticScoreSource = (TextView) view.findViewById(R.id.critic_score_source); TextView criticScoreText = (TextView) view.findViewById(R.id.critic_score_score); ImageView criticScoreLogo = (ImageView) view.findViewById(R.id.critic_score_logo); // set the appropriate information in the views if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { criticScoreSource.setText( Html.fromHtml(criticScore.source, Html.FROM_HTML_MODE_LEGACY)); } else { criticScoreSource.setText(Html.fromHtml(criticScore.source)); } criticScoreText.setText(criticScore.score); criticScoreSource.setVisibility(View.VISIBLE); criticScoreText.setVisibility(View.VISIBLE); ImageLoader.loadBitmap( mContext, criticScore.logoUrl, createCriticScoreLogoCallback(holder, programId, criticScoreLogo)); } private void onHorizontalScrolled() { if (mDetailInAnimator != null) { mHandler.removeCallbacks(mDetailInStarter); mHandler.postDelayed(mDetailInStarter, mAnimationDuration); } } } private static ImageLoaderCallback createCriticScoreLogoCallback( ProgramRowViewHolder holder, final long programId, ImageView logoView) { return new ImageLoaderCallback(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logoImage) { if (logoImage == null || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null || holder.mSelectedEntry.program.getId() != programId) { logoView.setVisibility(View.GONE); } else { logoView.setImageBitmap(logoImage); logoView.setVisibility(View.VISIBLE); } } }; } private static ImageLoaderCallback createProgramPosterArtCallback( ProgramRowViewHolder holder, final Program program) { return new ImageLoaderCallback(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap posterArt) { if (posterArt == null || holder.mSelectedEntry == null || holder.mSelectedEntry.program == null) { return; } String posterArtUri = holder.mSelectedEntry.program.getPosterArtUri(); if (posterArtUri == null || !posterArtUri.equals(program.getPosterArtUri())) { return; } holder.updatePosterArt(posterArt); } }; } private static ImageLoaderCallback createChannelLogoLoadedCallback( ProgramRowViewHolder holder, final long channelId) { return new ImageLoaderCallback(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { if (logo == null || holder.mChannel == null || holder.mChannel.getId() != channelId) { return; } holder.updateChannelLogo(logo); } }; } private static ImageLoaderCallback createTvInputLogoLoadedCallback( final TvInputInfo info, ProgramRowViewHolder holder) { return new ImageLoaderCallback(holder) { @Override public void onBitmapLoaded(ProgramRowViewHolder holder, @Nullable Bitmap logo) { if (logo != null && holder.mChannel != null && info.getId().equals(holder.mChannel.getInputId())) { holder.updateInputLogoInternal(logo); } } }; } }