blob: aed8b9002eb2d2d28ec06385a6f47e7a113bcb74 [file] [log] [blame]
/*
* 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<ProgramTableAdapter.ProgramRowViewHolder>
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<ProgramListAdapter> mProgramListAdapters = new ArrayList<>();
private final RecycledViewPool mRecycledViewPool;
// views to be be reused when displaying critic scores
private final List<LinearLayout> 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<Object> 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<CriticScore> 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<ProgramRowViewHolder> createCriticScoreLogoCallback(
ProgramRowViewHolder holder, final long programId, ImageView logoView) {
return new ImageLoaderCallback<ProgramRowViewHolder>(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<ProgramRowViewHolder> createProgramPosterArtCallback(
ProgramRowViewHolder holder, final Program program) {
return new ImageLoaderCallback<ProgramRowViewHolder>(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<ProgramRowViewHolder> createChannelLogoLoadedCallback(
ProgramRowViewHolder holder, final long channelId) {
return new ImageLoaderCallback<ProgramRowViewHolder>(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<ProgramRowViewHolder> createTvInputLogoLoadedCallback(
final TvInputInfo info, ProgramRowViewHolder holder) {
return new ImageLoaderCallback<ProgramRowViewHolder>(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);
}
}
};
}
}