diff options
Diffstat (limited to 'src/com/android/tv/menu')
23 files changed, 1073 insertions, 753 deletions
diff --git a/src/com/android/tv/menu/ActionCardView.java b/src/com/android/tv/menu/ActionCardView.java index 54892cac..2fd70bfb 100644 --- a/src/com/android/tv/menu/ActionCardView.java +++ b/src/com/android/tv/menu/ActionCardView.java @@ -19,8 +19,8 @@ package com.android.tv.menu; import android.content.Context; import android.util.AttributeSet; import android.util.Log; -import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.RelativeLayout; import android.widget.TextView; import com.android.tv.R; @@ -28,7 +28,7 @@ import com.android.tv.R; /** * A view to render an item of TV options. */ -public class ActionCardView extends FrameLayout implements ItemListRowView.CardView<MenuAction> { +public class ActionCardView extends RelativeLayout implements ItemListRowView.CardView<MenuAction> { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -66,7 +66,7 @@ public class ActionCardView extends FrameLayout implements ItemListRowView.CardV } mIconView.setImageDrawable(action.getDrawable(getContext())); mLabelView.setText(action.getActionName(getContext())); - mStateView.setText(action.getActionDescription(getContext())); + mStateView.setText(action.getActionDescription()); if (action.isEnabled()) { setEnabled(true); setFocusable(true); diff --git a/src/com/android/tv/menu/AppLinkCardView.java b/src/com/android/tv/menu/AppLinkCardView.java index bfb5e3f1..94ccd37f 100644 --- a/src/com/android/tv/menu/AppLinkCardView.java +++ b/src/com/android/tv/menu/AppLinkCardView.java @@ -24,6 +24,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.os.AsyncTask; import android.support.annotation.Nullable; import android.support.v7.graphics.Palette; import android.text.TextUtils; @@ -40,10 +41,12 @@ import com.android.tv.util.BitmapUtils; import com.android.tv.util.ImageLoader; import com.android.tv.util.TvInputManagerHelper; +import java.util.Objects; + /** * A view to render an app link card. */ -public class AppLinkCardView extends BaseCardView<Channel> { +public class AppLinkCardView extends BaseCardView<ChannelsRowItem> { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -53,9 +56,9 @@ public class AppLinkCardView extends BaseCardView<Channel> { private final int mIconHeight; private final int mIconPadding; private final int mIconColorFilter; + private final Drawable mDefaultDrawable; private ImageView mImageView; - private View mGradientView; private TextView mAppInfoView; private View mMetaViewHolder; private Channel mChannel; @@ -82,6 +85,7 @@ public class AppLinkCardView extends BaseCardView<Channel> { mPackageManager = context.getPackageManager(); mTvInputManagerHelper = ((MainActivity) context).getTvInputManagerHelper(); mIconColorFilter = getResources().getColor(R.color.app_link_card_icon_color_filter, null); + mDefaultDrawable = getResources().getDrawable(R.drawable.ic_recent_thumbnail_default, null); } /** @@ -92,65 +96,153 @@ public class AppLinkCardView extends BaseCardView<Channel> { } @Override - public void onBind(Channel channel, boolean selected) { + public void onBind(ChannelsRowItem item, boolean selected) { + Channel newChannel = item.getChannel(); + boolean channelChanged = !Objects.equals(mChannel, newChannel); + String previousPosterArtUri = mChannel == null ? null : mChannel.getAppLinkPosterArtUri(); + boolean posterArtChanged = previousPosterArtUri == null + || newChannel.getAppLinkPosterArtUri() == null + || !TextUtils.equals(previousPosterArtUri, newChannel.getAppLinkPosterArtUri()); + mChannel = newChannel; if (DEBUG) { - Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected + Log.d(TAG, "onBind(channelName=" + mChannel.getDisplayName() + ", selected=" + selected + ")"); } - mChannel = channel; ApplicationInfo appInfo = mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId()); - int linkType = mChannel.getAppLinkType(getContext()); - mIntent = mChannel.getAppLinkIntent(getContext()); + if (channelChanged) { + int linkType = mChannel.getAppLinkType(getContext()); + mIntent = mChannel.getAppLinkIntent(getContext()); - switch (linkType) { - case Channel.APP_LINK_TYPE_CHANNEL: - setText(mChannel.getAppLinkText()); - mAppInfoView.setVisibility(VISIBLE); - mGradientView.setVisibility(VISIBLE); - mAppInfoView.setCompoundDrawablePadding(mIconPadding); - mAppInfoView.setCompoundDrawables(null, null, null, null); - mAppInfoView.setText(mPackageManager.getApplicationLabel(appInfo)); - if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) { - mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON, - mIconWidth, mIconHeight, createChannelLogoCallback(this, mChannel, - Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON)); - } else if (appInfo.icon != 0) { - Drawable appIcon = mPackageManager.getApplicationIcon(appInfo); - BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); - appIcon.setBounds(0, 0, mIconWidth, mIconHeight); - mAppInfoView.setCompoundDrawables(appIcon, null, null, null); - } - break; - case Channel.APP_LINK_TYPE_APP: - setText(getContext().getString( - R.string.channels_item_app_link_app_launcher, - mPackageManager.getApplicationLabel(appInfo))); - mAppInfoView.setVisibility(GONE); - mGradientView.setVisibility(GONE); - break; - default: - mAppInfoView.setVisibility(GONE); - mGradientView.setVisibility(GONE); - Log.d(TAG, "Should not be here."); - } + CharSequence appLabel; + switch (linkType) { + case Channel.APP_LINK_TYPE_CHANNEL: + setText(mChannel.getAppLinkText()); + mAppInfoView.setVisibility(VISIBLE); + mAppInfoView.setCompoundDrawablePadding(mIconPadding); + mAppInfoView.setCompoundDrawablesRelative(null, null, null, null); + appLabel = mTvInputManagerHelper + .getTvInputApplicationLabel(mChannel.getInputId()); + if (appLabel != null) { + mAppInfoView.setText(appLabel); + } else { + new AsyncTask<Void, Void, CharSequence>() { + private final String mLoadTvInputId = mChannel.getInputId(); - if (mChannel.getAppLinkColor() == 0) { - mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background); - } else { - mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor()); - } + @Override + protected CharSequence doInBackground(Void... params) { + if (appInfo != null) { + return mPackageManager.getApplicationLabel(appInfo); + } + return null; + } - if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) { - mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); - mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART, - mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel, - Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART)); - } else { - setCardImageWithBanner(appInfo); + @Override + protected void onPostExecute(CharSequence appLabel) { + mTvInputManagerHelper.setTvInputApplicationLabel( + mLoadTvInputId, appLabel); + if (mLoadTvInputId != mChannel.getInputId() + || !isAttachedToWindow()) { + return; + } + mAppInfoView.setText(appLabel); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + if (!TextUtils.isEmpty(mChannel.getAppLinkIconUri())) { + mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON, + mIconWidth, mIconHeight, createChannelLogoCallback( + this, mChannel, Channel.LOAD_IMAGE_TYPE_APP_LINK_ICON)); + } else if (appInfo.icon != 0) { + Drawable appIcon = mTvInputManagerHelper + .getTvInputApplicationIcon(mChannel.getInputId()); + if (appIcon != null) { + BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); + appIcon.setBounds(0, 0, mIconWidth, mIconHeight); + mAppInfoView.setCompoundDrawablesRelative(appIcon, null, null, null); + } else { + new AsyncTask<Void, Void, Drawable>() { + private final String mLoadTvInputId = mChannel.getInputId(); + + @Override + protected Drawable doInBackground(Void... params) { + return mPackageManager.getApplicationIcon(appInfo); + } + + @Override + protected void onPostExecute(Drawable appIcon) { + mTvInputManagerHelper.setTvInputApplicationIcon( + mLoadTvInputId, appIcon); + if (!mLoadTvInputId.equals(mChannel.getInputId()) + || !isAttachedToWindow()) { + return; + } + BitmapUtils.setColorFilterToDrawable(mIconColorFilter, appIcon); + appIcon.setBounds(0, 0, mIconWidth, mIconHeight); + mAppInfoView.setCompoundDrawablesRelative( + appIcon, null, null, null); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + break; + case Channel.APP_LINK_TYPE_APP: + appLabel = mTvInputManagerHelper + .getTvInputApplicationLabel(mChannel.getInputId()); + if (appLabel != null) { + setText(getContext() + .getString(R.string.channels_item_app_link_app_launcher, appLabel)); + } else { + new AsyncTask<Void, Void, CharSequence>() { + private final String mLoadTvInputId = mChannel.getInputId(); + + @Override + protected CharSequence doInBackground(Void... params) { + if (appInfo != null) { + return mPackageManager.getApplicationLabel(appInfo); + } + return null; + } + + @Override + protected void onPostExecute(CharSequence appLabel) { + mTvInputManagerHelper.setTvInputApplicationLabel( + mLoadTvInputId, appLabel); + if (!mLoadTvInputId.equals(mChannel.getInputId()) + || !isAttachedToWindow()) { + return; + } + setText(getContext() + .getString( + R.string.channels_item_app_link_app_launcher, + appLabel)); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + mAppInfoView.setVisibility(GONE); + break; + default: + mAppInfoView.setVisibility(GONE); + Log.d(TAG, "Should not be here."); + } + + if (mChannel.getAppLinkColor() == 0) { + mMetaViewHolder.setBackgroundResource(R.color.channel_card_meta_background); + } else { + mMetaViewHolder.setBackgroundColor(mChannel.getAppLinkColor()); + } } - // Call super.onBind() at the end intentionally. In order to correctly handle extension of - // text view, text should be set before calling super.onBind. - super.onBind(channel, selected); + if (posterArtChanged) { + mImageView.setImageDrawable(mDefaultDrawable); + mImageView.setForeground(null); + if (!TextUtils.isEmpty(mChannel.getAppLinkPosterArtUri())) { + mChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART, + mCardImageWidth, mCardImageHeight, createChannelLogoCallback(this, mChannel, + Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART)); + } else { + setCardImageWithBanner(appInfo); + } + } + super.onBind(item, selected); } private static ImageLoader.ImageLoaderCallback<AppLinkCardView> createChannelLogoCallback( @@ -182,13 +274,14 @@ public class AppLinkCardView extends BaseCardView<Channel> { } } BitmapUtils.setColorFilterToDrawable(mIconColorFilter, drawable); - mAppInfoView.setCompoundDrawables(drawable, null, null, null); + mAppInfoView.setCompoundDrawablesRelative(drawable, null, null, null); } else if (type == Channel.LOAD_IMAGE_TYPE_APP_LINK_POSTER_ART) { if (bitmap == null) { setCardImageWithBanner( mTvInputManagerHelper.getTvInputAppInfo(mChannel.getInputId())); } else { mImageView.setImageBitmap(bitmap); + mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); if (mChannel.getAppLinkColor() == 0) { extractAndSetMetaViewBackgroundColor(bitmap); } @@ -200,7 +293,6 @@ public class AppLinkCardView extends BaseCardView<Channel> { protected void onFinishInflate() { super.onFinishInflate(); mImageView = (ImageView) findViewById(R.id.image); - mGradientView = findViewById(R.id.image_gradient); mAppInfoView = (TextView) findViewById(R.id.app_info); mMetaViewHolder = findViewById(R.id.app_link_text_holder); } @@ -209,37 +301,85 @@ public class AppLinkCardView extends BaseCardView<Channel> { // 1) Provided poster art image, 2) Activity banner, 3) Activity icon, 4) Application banner, // 5) Application icon, and 6) default image. private void setCardImageWithBanner(ApplicationInfo appInfo) { - Drawable banner = null; - if (mIntent != null) { - try { - banner = mPackageManager.getActivityBanner(mIntent); - if (banner == null) { - banner = mPackageManager.getActivityIcon(mIntent); + new AsyncTask<Void, Void, Drawable>() { + private String mLoadTvInputId = mChannel.getInputId(); + @Override + protected Drawable doInBackground(Void... params) { + Drawable banner = null; + if (mIntent != null) { + try { + banner = mPackageManager.getActivityBanner(mIntent); + if (banner == null) { + banner = mPackageManager.getActivityIcon(mIntent); + } + } catch (PackageManager.NameNotFoundException e) { + // do nothing. + } } - } catch (PackageManager.NameNotFoundException e) { - // do nothing. + return banner; } - } - if (banner == null && appInfo != null) { - if (appInfo.banner != 0) { - banner = mPackageManager.getApplicationBanner(appInfo); - } - if (banner == null && appInfo.icon != 0) { - banner = mPackageManager.getApplicationIcon(appInfo); + @Override + protected void onPostExecute(Drawable banner) { + if (mLoadTvInputId != mChannel.getInputId() || !isAttachedToWindow()) { + return; + } + if (banner != null) { + setCardImageWithBannerInternal(banner); + } else { + setCardImageWithApplicationInfoBanner(appInfo); + } } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + private void setCardImageWithApplicationInfoBanner(ApplicationInfo appInfo) { + Drawable appBanner = + mTvInputManagerHelper.getTvInputApplicationBanner(mChannel.getInputId()); + if (appBanner != null) { + setCardImageWithBannerInternal(appBanner); + } else { + new AsyncTask<Void, Void, Drawable>() { + private final String mLoadTvInputId = mChannel.getInputId(); + @Override + protected Drawable doInBackground(Void... params) { + Drawable banner = null; + if (appInfo != null) { + if (appInfo.banner != 0) { + banner = mPackageManager.getApplicationBanner(appInfo); + } + if (banner == null && appInfo.icon != 0) { + banner = mPackageManager.getApplicationIcon(appInfo); + } + } + return banner; + } + + @Override + protected void onPostExecute(Drawable banner) { + mTvInputManagerHelper.setTvInputApplicationBanner(mLoadTvInputId, banner); + if (!TextUtils.equals(mLoadTvInputId, mChannel.getInputId()) + || !isAttachedToWindow()) { + return; + } + setCardImageWithBannerInternal(banner); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + } + private void setCardImageWithBannerInternal(Drawable banner) { if (banner == null) { - mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); + mImageView.setImageDrawable(mDefaultDrawable); mImageView.setBackgroundResource(R.color.channel_card); } else { - Bitmap bitmap = - Bitmap.createBitmap(mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888); + Bitmap bitmap = Bitmap.createBitmap( + mCardImageWidth, mCardImageHeight, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); banner.setBounds(0, 0, mCardImageWidth, mCardImageHeight); banner.draw(canvas); mImageView.setImageDrawable(banner); + mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); if (mChannel.getAppLinkColor() == 0) { extractAndSetMetaViewBackgroundColor(bitmap); } diff --git a/src/com/android/tv/menu/BaseCardView.java b/src/com/android/tv/menu/BaseCardView.java index c6a34a5d..4c5e6c78 100644 --- a/src/com/android/tv/menu/BaseCardView.java +++ b/src/com/android/tv/menu/BaseCardView.java @@ -22,6 +22,7 @@ import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Outline; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -35,9 +36,6 @@ import com.android.tv.R; * A base class to render a card. */ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRowView.CardView<T> { - private static final String TAG = "BaseCardView"; - private static final boolean DEBUG = false; - private static final float SCALE_FACTOR_0F = 0f; private static final float SCALE_FACTOR_1F = 1f; @@ -57,6 +55,11 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo private TextView mTextViewFocused; private final int mCardImageWidth; private final float mCardHeight; + private boolean mSelected; + + private int mTextResId; + private String mTextString; + private boolean mTextChanged; public BaseCardView(Context context) { this(context, null); @@ -103,23 +106,9 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo /** * Called when the view is displayed. - * - * Before onBind is called, this view's text should be set to determine if it'll be extended - * or not in focus state. */ @Override public void onBind(T item, boolean selected) { - if (mTextView != null && mTextViewFocused != null) { - mTextViewFocused.measure( - MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1; - if (mExtendViewOnFocus) { - setTextViewFocusedAlpha(selected ? 1f : 0f); - } else { - setTextViewFocusedAlpha(1f); - } - } setFocusAnimatedValue(selected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F); } @@ -128,6 +117,7 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo @Override public void onSelected() { + mSelected = true; if (isAttachedToWindow() && getVisibility() == View.VISIBLE) { startFocusAnimation(SCALE_FACTOR_1F); } else { @@ -138,6 +128,7 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo @Override public void onDeselected() { + mSelected = false; if (isAttachedToWindow() && getVisibility() == View.VISIBLE) { startFocusAnimation(SCALE_FACTOR_0F); } else { @@ -150,11 +141,17 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo * Sets text of this card view. */ public void setText(int resId) { - if (mTextViewFocused != null) { - mTextViewFocused.setText(resId); - } - if (mTextView != null) { - mTextView.setText(resId); + if (mTextResId != resId) { + mTextResId = resId; + mTextString = null; + mTextChanged = true; + if (mTextViewFocused != null) { + mTextViewFocused.setText(resId); + } + if (mTextView != null) { + mTextView.setText(resId); + } + onTextViewUpdated(); } } @@ -162,12 +159,33 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo * Sets text of this card view. */ public void setText(String text) { - if (mTextViewFocused != null) { - mTextViewFocused.setText(text); + if (!TextUtils.equals(text, mTextString)) { + mTextString = text; + mTextResId = 0; + mTextChanged = true; + if (mTextViewFocused != null) { + mTextViewFocused.setText(text); + } + if (mTextView != null) { + mTextView.setText(text); + } + onTextViewUpdated(); } - if (mTextView != null) { - mTextView.setText(text); + } + + private void onTextViewUpdated() { + if (mTextView != null && mTextViewFocused != null) { + mTextViewFocused.measure( + MeasureSpec.makeMeasureSpec(mCardImageWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); + mExtendViewOnFocus = mTextViewFocused.getLineCount() > 1; + if (mExtendViewOnFocus) { + setTextViewFocusedAlpha(mSelected ? 1f : 0f); + } else { + setTextViewFocusedAlpha(1f); + } } + setFocusAnimatedValue(mSelected ? SCALE_FACTOR_1F : SCALE_FACTOR_0F); } /** @@ -209,12 +227,18 @@ public abstract class BaseCardView<T> extends LinearLayout implements ItemListRo setScaleX(scale); setScaleY(scale); setTranslationZ(mFocusTranslationZ * animatedValue); - if (mExtendViewOnFocus) { + if (mTextView != null && mTextViewFocused != null) { ViewGroup.LayoutParams params = mTextView.getLayoutParams(); - params.height = Math.round(mTextViewHeight - + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue); - setTextViewLayoutParams(params); - setTextViewFocusedAlpha(animatedValue); + int height = mExtendViewOnFocus ? Math.round(mTextViewHeight + + (mExtendedTextViewHeight - mTextViewHeight) * animatedValue) + : (int) mTextViewHeight; + if (height != params.height) { + params.height = height; + setTextViewLayoutParams(params); + } + if (mExtendViewOnFocus) { + setTextViewFocusedAlpha(animatedValue); + } } } diff --git a/src/com/android/tv/menu/ChannelCardView.java b/src/com/android/tv/menu/ChannelCardView.java index 1c8015a6..2ecb6af7 100644 --- a/src/com/android/tv/menu/ChannelCardView.java +++ b/src/com/android/tv/menu/ChannelCardView.java @@ -34,10 +34,12 @@ import com.android.tv.data.Program; import com.android.tv.parental.ParentalControlSettings; import com.android.tv.util.ImageLoader; +import java.util.Objects; + /** * A view to render channel card. */ -public class ChannelCardView extends BaseCardView<Channel> { +public class ChannelCardView extends BaseCardView<ChannelsRowItem> { private static final String TAG = MenuView.TAG; private static final boolean DEBUG = MenuView.DEBUG; @@ -45,11 +47,11 @@ public class ChannelCardView extends BaseCardView<Channel> { private final int mCardImageHeight; private ImageView mImageView; - private View mGradientView; private TextView mChannelNumberNameView; private ProgressBar mProgressBar; private Channel mChannel; private Program mProgram; + private String mPosterArtUri; private final MainActivity mMainActivity; public ChannelCardView(Context context) { @@ -71,39 +73,72 @@ public class ChannelCardView extends BaseCardView<Channel> { protected void onFinishInflate() { super.onFinishInflate(); mImageView = (ImageView) findViewById(R.id.image); - mGradientView = findViewById(R.id.image_gradient); + mImageView.setBackgroundResource(R.color.channel_card); mChannelNumberNameView = (TextView) findViewById(R.id.channel_number_and_name); mProgressBar = (ProgressBar) findViewById(R.id.progress); } @Override - public void onBind(Channel channel, boolean selected) { + public void onBind(ChannelsRowItem item, boolean selected) { if (DEBUG) { - Log.d(TAG, "onBind(channelName=" + channel.getDisplayName() + ", selected=" + selected - + ")"); + Log.d(TAG, "onBind(channelName=" + item.getChannel().getDisplayName() + ", selected=" + + selected + ")"); } - mChannel = channel; - mProgram = null; - mChannelNumberNameView.setText(mChannel.getDisplayText()); - mChannelNumberNameView.setVisibility(VISIBLE); - mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); - mImageView.setBackgroundResource(R.color.channel_card); - mGradientView.setVisibility(View.GONE); - mProgressBar.setVisibility(GONE); + updateChannel(item); + updateProgram(); + super.onBind(item, selected); + } - setTextViewEnabled(true); - if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled() - && mChannel.isLocked()) { + private void updateChannel(ChannelsRowItem item) { + if (!item.getChannel().equals(mChannel)) { + mChannel = item.getChannel(); + mChannelNumberNameView.setText(mChannel.getDisplayText()); + mChannelNumberNameView.setVisibility(VISIBLE); + } + } + + private void updateProgram() { + ParentalControlSettings parental = mMainActivity.getParentalControlSettings(); + if (parental.isParentalControlsEnabled() && mChannel.isLocked()) { setText(R.string.program_title_for_blocked_channel); - return; + mProgram = null; + } else { + Program currentProgram = + mMainActivity.getProgramDataManager().getCurrentProgram(mChannel.getId()); + if (!Objects.equals(currentProgram, mProgram)) { + mProgram = currentProgram; + if (mProgram == null || TextUtils.isEmpty(mProgram.getTitle())) { + setTextViewEnabled(false); + setText(R.string.program_title_for_no_information); + } else { + setTextViewEnabled(true); + setText(mProgram.getTitle()); + } + } + } + if (mProgram == null) { + mProgressBar.setVisibility(GONE); + setPosterArt(null); } else { - setText(""); + // Update progress. + mProgressBar.setVisibility(View.VISIBLE); + long startTime = mProgram.getStartTimeUtcMillis(); + long endTime = mProgram.getEndTimeUtcMillis(); + long currTime = System.currentTimeMillis(); + if (currTime <= startTime) { + mProgressBar.setProgress(0); + } else if (currTime >= endTime) { + mProgressBar.setProgress(100); + } else { + mProgressBar.setProgress( + (int) (100 * (currTime - startTime) / (endTime - startTime))); + } + // Update image. + if (!parental.isParentalControlsEnabled() + || !parental.isRatingBlocked(mProgram.getContentRatings())) { + setPosterArt(mProgram.getPosterArtUri()); + } } - - updateProgramInformation(); - // Call super.onBind() at the end intentionally. In order to correctly handle extension of - // text view, text should be set before calling super.onBind. - super.onBind(channel, selected); } private static ImageLoader.ImageLoaderCallback<ChannelCardView> createProgramPosterArtCallback( @@ -121,49 +156,20 @@ public class ChannelCardView extends BaseCardView<Channel> { }; } - private void updatePosterArt(Bitmap posterArt) { - mImageView.setImageBitmap(posterArt); - mGradientView.setVisibility(View.VISIBLE); - } - - private void updateProgramInformation() { - if (mChannel == null) { - return; - } - mProgram = mMainActivity.getProgramDataManager().getCurrentProgram(mChannel.getId()); - if (mProgram == null || TextUtils.isEmpty(mProgram.getTitle())) { - setTextViewEnabled(false); - setText(R.string.program_title_for_no_information); - } else { - setText(mProgram.getTitle()); - } - - if (mProgram == null) { - return; - } - - long startTime = mProgram.getStartTimeUtcMillis(); - long endTime = mProgram.getEndTimeUtcMillis(); - long currTime = System.currentTimeMillis(); - mProgressBar.setVisibility(View.VISIBLE); - if (currTime <= startTime) { - mProgressBar.setProgress(0); - } else if (currTime >= endTime) { - mProgressBar.setProgress(100); - } else { - mProgressBar.setProgress((int) (100 * (currTime - startTime) / (endTime - startTime))); + private void setPosterArt(String posterArtUri) { + if (!TextUtils.equals(mPosterArtUri, posterArtUri)) { + mPosterArtUri = posterArtUri; + if (posterArtUri == null + || !mProgram.loadPosterArt(getContext(), mCardImageWidth, mCardImageHeight, + createProgramPosterArtCallback(this, mProgram))) { + mImageView.setImageResource(R.drawable.ic_recent_thumbnail_default); + mImageView.setForeground(null); + } } + } - if (!(getContext() instanceof MainActivity)) { - Log.e(TAG, "Fails to check program's content rating."); - return; - } - ParentalControlSettings parental = mMainActivity.getParentalControlSettings(); - if ((!parental.isParentalControlsEnabled() - || !parental.isRatingBlocked(mProgram.getContentRatings())) - && !TextUtils.isEmpty(mProgram.getPosterArtUri())) { - mProgram.loadPosterArt(getContext(), mCardImageWidth, mCardImageHeight, - createProgramPosterArtCallback(this, mProgram)); - } + private void updatePosterArt(Bitmap posterArt) { + mImageView.setImageBitmap(posterArt); + mImageView.setForeground(getContext().getDrawable(R.drawable.card_image_gradient)); } -} +}
\ No newline at end of file diff --git a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java index f932d75d..bc5d6cfb 100644 --- a/src/com/android/tv/menu/ChannelsPosterPrefetcher.java +++ b/src/com/android/tv/menu/ChannelsPosterPrefetcher.java @@ -97,12 +97,13 @@ public class ChannelsPosterPrefetcher { // This executes on the main thread, but since the item list is expected to be about 5 items // and ImageLoader spawns an async task so this is fast enough. 1 ms in local testing. - List<Channel> channelList = mChannelsAdapter.getItemList(); - if (channelList != null) { - for (Channel channel : channelList) { + List<ChannelsRowItem> items = mChannelsAdapter.getItemList(); + if (items != null) { + for (ChannelsRowItem item : items) { if (isCanceled) { return; } + Channel channel = item.getChannel(); if (!Channel.isValid(channel)) { continue; } @@ -116,7 +117,7 @@ public class ChannelsPosterPrefetcher { } if (DEBUG) { Log.d(TAG, "doPrefetchImages() finished. ImageLoader may still have async tasks for " - + "channels " + channelList); + + "channels " + items); } } diff --git a/src/com/android/tv/menu/ChannelsRow.java b/src/com/android/tv/menu/ChannelsRow.java index dedf0993..490d73de 100644 --- a/src/com/android/tv/menu/ChannelsRow.java +++ b/src/com/android/tv/menu/ChannelsRow.java @@ -26,8 +26,14 @@ import com.android.tv.recommendation.Recommender; public class ChannelsRow extends ItemListRow { public static final String ID = ChannelsRow.class.getName(); - private static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5; - private static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10; + /** + * Minimum count for recent channels. + */ + public static final int MIN_COUNT_FOR_RECENT_CHANNELS = 5; + /** + * Maximum count for recent channels. + */ + public static final int MAX_COUNT_FOR_RECENT_CHANNELS = 10; private Recommender mTvRecommendation; private ChannelsRowAdapter mChannelsAdapter; diff --git a/src/com/android/tv/menu/ChannelsRowAdapter.java b/src/com/android/tv/menu/ChannelsRowAdapter.java index c8e1bd05..7ff44ea6 100644 --- a/src/com/android/tv/menu/ChannelsRowAdapter.java +++ b/src/com/android/tv/menu/ChannelsRowAdapter.java @@ -31,17 +31,15 @@ import com.android.tv.dvr.DvrDataManager; import com.android.tv.recommendation.Recommender; import com.android.tv.util.SetupUtils; import com.android.tv.util.TvInputManagerHelper; -import com.android.tv.util.Utils; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.List; /** * An adapter of the Channels row. */ -public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> { - private static final String TAG = "ChannelsRowAdapter"; - +public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<ChannelsRowItem> { // There are four special cards: guide, setup, dvr, applink. private static final int SIZE_OF_VIEW_TYPE = 5; @@ -51,7 +49,6 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> private final DvrDataManager mDvrDataManager; private final int mMaxCount; private final int mMinCount; - private final int[] mViewType = new int[SIZE_OF_VIEW_TYPE]; private final View.OnClickListener mGuideOnClickListener = new View.OnClickListener() { @Override @@ -113,14 +110,12 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> mRecommender = recommender; mMinCount = minCount; mMaxCount = maxCount; + setHasStableIds(true); } @Override public int getItemViewType(int position) { - if (position >= SIZE_OF_VIEW_TYPE) { - return R.layout.menu_card_channel; - } - return mViewType[position]; + return getItemList().get(position).getLayoutId(); } @Override @@ -129,9 +124,12 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> } @Override - public void onBindViewHolder(MyViewHolder viewHolder, int position) { - super.onBindViewHolder(viewHolder, position); + public long getItemId(int position) { + return getItemList().get(position).getItemId(); + } + @Override + public void onBindViewHolder(MyViewHolder viewHolder, int position) { int viewType = getItemViewType(position); if (viewType == R.layout.menu_card_guide) { viewHolder.itemView.setOnClickListener(mGuideOnClickListener); @@ -144,80 +142,158 @@ public class ChannelsRowAdapter extends ItemListRowView.ItemListAdapter<Channel> SimpleCardView view = (SimpleCardView) viewHolder.itemView; view.setText(R.string.channels_item_dvr); } else { - viewHolder.itemView.setTag(getItemList().get(position)); + viewHolder.itemView.setTag(getItemList().get(position).getChannel()); viewHolder.itemView.setOnClickListener(mChannelOnClickListener); } + super.onBindViewHolder(viewHolder, position); } @Override public void update() { - List<Channel> channelList = new ArrayList<>(); - Channel dummyChannel = new Channel.Builder().build(); - // For guide item - channelList.add(dummyChannel); - // For setup item - TvInputManagerHelper inputManager = TvApplication.getSingletons(mContext) - .getTvInputManagerHelper(); - boolean showSetupCard = SetupUtils.getInstance(mContext).hasNewInput(inputManager); - Channel currentChannel = getMainActivity().getCurrentChannel(); - boolean showAppLinkCard = currentChannel != null - && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE - // Sometimes applicationInfo can be null. b/28932537 - && inputManager.getTvInputAppInfo(currentChannel.getInputId()) != null; - boolean showDvrCard = false; + if (getItemCount() == 0) { + createItems(); + } else { + updateItems(); + } + } + + private void createItems() { + List<ChannelsRowItem> items = new ArrayList<>(); + items.add(ChannelsRowItem.GUIDE_ITEM); + if (needToShowSetupItem()) { + items.add(ChannelsRowItem.SETUP_ITEM); + } + if (needToShowDvrItem()) { + items.add(ChannelsRowItem.DVR_ITEM); + } + if (needToShowAppLinkItem()) { + ChannelsRowItem.APP_LINK_ITEM.setChannel( + new Channel.Builder(getMainActivity().getCurrentChannel()).build()); + items.add(ChannelsRowItem.APP_LINK_ITEM); + } + for (Channel channel : getRecentChannels()) { + items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel)); + } + setItemList(items); + } + + private void updateItems() { + List<ChannelsRowItem> items = getItemList(); + // The current index of the item list to iterate. It starts from 1 because the first item + // (GUIDE) is always visible and not updated. + int currentIndex = 1; + if (updateItem(needToShowSetupItem(), ChannelsRowItem.SETUP_ITEM, currentIndex)) { + ++currentIndex; + } + if (updateItem(needToShowDvrItem(), ChannelsRowItem.DVR_ITEM, currentIndex)) { + ++currentIndex; + } + if (updateItem(needToShowAppLinkItem(), ChannelsRowItem.APP_LINK_ITEM, currentIndex)) { + if (!getMainActivity().getCurrentChannel() + .hasSameReadOnlyInfo(ChannelsRowItem.APP_LINK_ITEM.getChannel())) { + ChannelsRowItem.APP_LINK_ITEM.setChannel( + new Channel.Builder(getMainActivity().getCurrentChannel()).build()); + notifyItemChanged(currentIndex); + } + ++currentIndex; + } + int numOldChannels = items.size() - currentIndex; + if (numOldChannels > 0) { + while (items.size() > currentIndex) { + items.remove(items.size() - 1); + } + notifyItemRangeRemoved(currentIndex, numOldChannels); + } + for (Channel channel : getRecentChannels()) { + items.add(new ChannelsRowItem(channel, R.layout.menu_card_channel)); + } + int numNewChannels = items.size() - currentIndex; + if (numNewChannels > 0) { + notifyItemRangeInserted(currentIndex, numNewChannels); + } + } + + /** + * Returns {@code true} if the item should be shown. + */ + private boolean updateItem(boolean needToShow, ChannelsRowItem item, int index) { + List<ChannelsRowItem> items = getItemList(); + boolean isItemInList = index < items.size() && item.equals(items.get(index)); + if (needToShow && !isItemInList) { + items.add(index, item); + notifyItemInserted(index); + } else if (!needToShow && isItemInList) { + items.remove(index); + notifyItemRemoved(index); + } + return needToShow; + } + + private boolean needToShowSetupItem() { + TvInputManagerHelper inputManager = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + return SetupUtils.getInstance(mContext).hasNewInput(inputManager); + } + + private boolean needToShowDvrItem() { + TvInputManagerHelper inputManager = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); if (mDvrDataManager != null) { for (TvInputInfo info : inputManager.getTvInputInfos(true, true)) { if (info.canRecord()) { - showDvrCard = true; - break; + return true; } } } + return false; + } - mViewType[0] = R.layout.menu_card_guide; - int index = 1; - if (showSetupCard) { - channelList.add(dummyChannel); - mViewType[index++] = R.layout.menu_card_setup; - } - if (showDvrCard) { - channelList.add(dummyChannel); - mViewType[index++] = R.layout.menu_card_dvr; - } - if (showAppLinkCard) { - channelList.add(currentChannel); - mViewType[index++] = R.layout.menu_card_app_link; - } - for ( ; index < mViewType.length; ++index) { - mViewType[index] = R.layout.menu_card_channel; - } - channelList.addAll(getRecentChannels()); - setItemList(channelList); + private boolean needToShowAppLinkItem() { + TvInputManagerHelper inputManager = + TvApplication.getSingletons(mContext).getTvInputManagerHelper(); + Channel currentChannel = getMainActivity().getCurrentChannel(); + return currentChannel != null + && currentChannel.getAppLinkType(mContext) != Channel.APP_LINK_TYPE_NONE + // Sometimes applicationInfo can be null. b/28932537 + && inputManager.getTvInputAppInfo(currentChannel.getInputId()) != null; } private List<Channel> getRecentChannels() { List<Channel> channelList = new ArrayList<>(); + long currentChannelId = getMainActivity().getCurrentChannelId(); + ArrayDeque<Long> recentChannels = getMainActivity().getRecentChannels(); + // Add the last watched channel as the first one. + for (long channelId : recentChannels) { + if (addChannelToList( + channelList, mRecommender.getChannel(channelId), currentChannelId)) { + break; + } + } + // Add the recommended channels. for (Channel channel : mRecommender.recommendChannels(mMaxCount)) { - if (channel.isBrowsable()) { - channelList.add(channel); + if (channelList.size() >= mMaxCount) { + break; } + addChannelToList(channelList, channel, currentChannelId); } - int count = channelList.size(); // If the number of recommended channels is not enough, add more from the recent channel // list. - if (count < mMinCount) { - for (long channelId : getMainActivity().getRecentChannels()) { - Channel channel = mRecommender.getChannel(channelId); - if (channel == null || channelList.contains(channel) - || !channel.isBrowsable()) { - continue; - } - channelList.add(channel); - if (++count >= mMinCount) { - break; - } + for (long channelId : recentChannels) { + if (channelList.size() >= mMinCount) { + break; } + addChannelToList(channelList, mRecommender.getChannel(channelId), currentChannelId); } return channelList; } + + private static boolean addChannelToList( + List<Channel> channelList, Channel channel, long currentChannelId) { + if (channel == null || channel.getId() == currentChannelId + || channelList.contains(channel) || !channel.isBrowsable()) { + return false; + } + channelList.add(channel); + return true; + } } diff --git a/src/com/android/tv/menu/ChannelsRowItem.java b/src/com/android/tv/menu/ChannelsRowItem.java new file mode 100644 index 00000000..c35189ec --- /dev/null +++ b/src/com/android/tv/menu/ChannelsRowItem.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2017 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.menu; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.android.tv.R; +import com.android.tv.data.Channel; + +/** + * A class for the items in channels row. + */ +public class ChannelsRowItem { + /** The item ID for guide item */ + public static final int GUIDE_ITEM_ID = -1; + /** The item ID for setup item */ + public static final int SETUP_ITEM_ID = -2; + /** The item ID for DVR item */ + public static final int DVR_ITEM_ID = -3; + /** The item ID for app link item */ + public static final int APP_LINK_ITEM_ID = -4; + + /** The item which represents the guide. */ + public static final ChannelsRowItem GUIDE_ITEM = + new ChannelsRowItem(GUIDE_ITEM_ID, R.layout.menu_card_guide); + /** The item which represents the setup. */ + public static final ChannelsRowItem SETUP_ITEM = + new ChannelsRowItem(SETUP_ITEM_ID, R.layout.menu_card_setup); + /** The item which represents the DVR. */ + public static final ChannelsRowItem DVR_ITEM = + new ChannelsRowItem(DVR_ITEM_ID, R.layout.menu_card_dvr); + /** The item which represents the app link. */ + public static final ChannelsRowItem APP_LINK_ITEM = + new ChannelsRowItem(APP_LINK_ITEM_ID, R.layout.menu_card_app_link); + + private final long mItemId; + @NonNull private Channel mChannel; + private final int mLayoutId; + + public ChannelsRowItem(@NonNull Channel channel, int layoutId) { + this(channel.getId(), layoutId); + mChannel = channel; + } + + private ChannelsRowItem(long itemId, int layoutId) { + mItemId = itemId; + mLayoutId = layoutId; + } + + /** + * Returns the channel for this item. + */ + @NonNull + public Channel getChannel() { + return mChannel; + } + + /** + * Sets the channel. + */ + public void setChannel(@NonNull Channel channel) { + mChannel = channel; + } + + /** + * Returns the layout resource ID to represent this item. + */ + public int getLayoutId() { + return mLayoutId; + } + + /** + * Returns the unique ID for this item. + */ + public long getItemId() { + return mItemId; + } + + @Override + public String toString() { + return "ChannelsRowItem{" + + "itemId=" + mItemId + + ", layoutId=" + mLayoutId + + ", channel=" + mChannel + "}"; + } +} diff --git a/src/com/android/tv/menu/ItemListRowView.java b/src/com/android/tv/menu/ItemListRowView.java index 4919c595..cbeee936 100644 --- a/src/com/android/tv/menu/ItemListRowView.java +++ b/src/com/android/tv/menu/ItemListRowView.java @@ -28,6 +28,7 @@ import android.view.ViewGroup; import com.android.tv.MainActivity; import com.android.tv.R; +import com.android.tv.util.ViewCache; import java.util.Collections; import java.util.List; @@ -69,6 +70,8 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe protected void onFinishInflate() { super.onFinishInflate(); mListView = (HorizontalGridView) getContentsView(); + // Disable the position change animation of the cards. + mListView.setItemAnimator(null); } @Override @@ -194,9 +197,24 @@ public class ItemListRowView extends MenuRowView implements OnChildSelectedListe return mItemList.size(); } + /** + * Returns the position of the item. + */ + protected int getItemPosition(T item) { + return mItemList.indexOf(item); + } + + /** + * Returns {@code true} if the item list contains the item, otherwise {@code false}. + */ + protected boolean containsItem(T item) { + return mItemList.contains(item); + } + @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View view = mLayoutInflater.inflate(getLayoutResId(viewType), parent, false); + View view = ViewCache.getInstance().getOrCreateView( + mLayoutInflater, getLayoutResId(viewType), parent); return new MyViewHolder(view); } diff --git a/src/com/android/tv/menu/Menu.java b/src/com/android/tv/menu/Menu.java index 1160a5b5..e373de61 100644 --- a/src/com/android/tv/menu/Menu.java +++ b/src/com/android/tv/menu/Menu.java @@ -26,24 +26,28 @@ import android.os.Message; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; +import android.support.v17.leanback.widget.HorizontalGridView; import android.util.Log; import com.android.tv.ChannelTuner; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.analytics.DurationTimer; +import com.android.tv.TvOptionsManager; import com.android.tv.analytics.Tracker; import com.android.tv.common.TvCommonUtils; import com.android.tv.common.WeakHandler; import com.android.tv.menu.MenuRowFactory.PartnerRow; -import com.android.tv.menu.MenuRowFactory.PipOptionsRow; import com.android.tv.menu.MenuRowFactory.TvOptionsRow; import com.android.tv.ui.TunableTvView; +import com.android.tv.util.DurationTimer; +import com.android.tv.util.ViewCache; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * A class which controls the menu. @@ -81,10 +85,21 @@ public class Menu { sRowIdListForReason.add(PlayControlsRow.ID); // REASON_PLAY_CONTROLS_JUMP_TO_NEXT } + private static final Map<Integer, Integer> PRELOAD_VIEW_IDS = new HashMap<>(); + static { + PRELOAD_VIEW_IDS.put(R.layout.menu_card_guide, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_setup, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_dvr, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_app_link, 1); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_channel, ChannelsRow.MAX_COUNT_FOR_RECENT_CHANNELS); + PRELOAD_VIEW_IDS.put(R.layout.menu_card_action, 7); + } + private static final String SCREEN_NAME = "Menu"; private static final int MSG_HIDE_MENU = 1000; + private final Context mContext; private final IMenuView mMenuView; private final Tracker mTracker; private final DurationTimer mVisibleTimer = new DurationTimer(); @@ -103,15 +118,16 @@ public class Menu { @VisibleForTesting Menu(Context context, IMenuView menuView, MenuRowFactory menuRowFactory, OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) { - this(context, null, menuView, menuRowFactory, onMenuVisibilityChangeListener); + this(context, null, null, menuView, menuRowFactory, onMenuVisibilityChangeListener); } - public Menu(Context context, TunableTvView tvView, IMenuView menuView, - MenuRowFactory menuRowFactory, + public Menu(Context context, TunableTvView tvView, TvOptionsManager optionsManager, + IMenuView menuView, MenuRowFactory menuRowFactory, OnMenuVisibilityChangeListener onMenuVisibilityChangeListener) { + mContext = context; mMenuView = menuView; mTracker = TvApplication.getSingletons(context).getTracker(); - mMenuUpdater = new MenuUpdater(context, tvView, this); + mMenuUpdater = new MenuUpdater(this, tvView, optionsManager); Resources res = context.getResources(); mShowDurationMillis = res.getInteger(R.integer.menu_show_duration); mOnMenuVisibilityChangeListener = onMenuVisibilityChangeListener; @@ -130,7 +146,6 @@ public class Menu { addMenuRow(menuRowFactory.createMenuRow(this, ChannelsRow.class)); addMenuRow(menuRowFactory.createMenuRow(this, PartnerRow.class)); addMenuRow(menuRowFactory.createMenuRow(this, TvOptionsRow.class)); - addMenuRow(menuRowFactory.createMenuRow(this, PipOptionsRow.class)); mMenuView.setMenuRows(mMenuRows); } @@ -160,6 +175,16 @@ public class Menu { } /** + * Preloads the item view used for the menu. + */ + public void preloadItemViews() { + HorizontalGridView fakeParent = new HorizontalGridView(mContext); + for (int id : PRELOAD_VIEW_IDS.keySet()) { + ViewCache.getInstance().putView(mContext, id, fakeParent, PRELOAD_VIEW_IDS.get(id)); + } + } + + /** * Shows the main menu. * * @param reason A reason why this is called. See {@link MenuShowReason} @@ -293,9 +318,7 @@ public class Menu { */ public void onStreamInfoChanged() { if (DEBUG) Log.d(TAG, "update options row in main menu"); - for (MenuRow row : mMenuRows) { - row.onStreamInfoChanged(); - } + mMenuUpdater.onStreamInfoChanged(); } @VisibleForTesting diff --git a/src/com/android/tv/menu/MenuAction.java b/src/com/android/tv/menu/MenuAction.java index 0d59552a..b4356059 100644 --- a/src/com/android/tv/menu/MenuAction.java +++ b/src/com/android/tv/menu/MenuAction.java @@ -20,9 +20,9 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.text.TextUtils; -import com.android.tv.MainActivity; import com.android.tv.R; import com.android.tv.TvOptionsManager; +import com.android.tv.TvOptionsManager.OptionType; /** * A class to define possible actions from main menu. @@ -36,12 +36,9 @@ public class MenuAction { public static final MenuAction SELECT_DISPLAY_MODE_ACTION = new MenuAction(R.string.options_item_display_mode, TvOptionsManager.OPTION_DISPLAY_MODE, R.drawable.ic_tvoption_aspect); - public static final MenuAction PIP_IN_APP_ACTION = - new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_IN_APP_PIP, - R.drawable.ic_tvoption_pip); public static final MenuAction SYSTEMWIDE_PIP_ACTION = new MenuAction(R.string.options_item_pip, TvOptionsManager.OPTION_SYSTEMWIDE_PIP, - R.drawable.ic_pip_option_layout2); + R.drawable.ic_tvoption_pip); public static final MenuAction SELECT_AUDIO_LANGUAGE_ACTION = new MenuAction(R.string.options_item_multi_audio, TvOptionsManager.OPTION_MULTI_AUDIO, R.drawable.ic_tvoption_multi_track); @@ -51,34 +48,36 @@ public class MenuAction { public static final MenuAction DEV_ACTION = new MenuAction(R.string.options_item_developer, TvOptionsManager.OPTION_DEVELOPER, R.drawable.ic_developer_mode_tv_white_48dp); - // TODO: Change the icon. public static final MenuAction SETTINGS_ACTION = new MenuAction(R.string.options_item_settings, TvOptionsManager.OPTION_SETTINGS, R.drawable.ic_settings); - // Actions in the PIP option row. - public static final MenuAction PIP_SELECT_INPUT_ACTION = - new MenuAction(R.string.pip_options_item_source, TvOptionsManager.OPTION_PIP_INPUT, - R.drawable.ic_pip_option_input); - public static final MenuAction PIP_SWAP_ACTION = - new MenuAction(R.string.pip_options_item_swap, TvOptionsManager.OPTION_PIP_SWAP, - R.drawable.ic_pip_option_swap); - public static final MenuAction PIP_SOUND_ACTION = - new MenuAction(R.string.pip_options_item_sound, TvOptionsManager.OPTION_PIP_SOUND, - R.drawable.ic_pip_option_swap_audio); - public static final MenuAction PIP_LAYOUT_ACTION = - new MenuAction(R.string.pip_options_item_layout, TvOptionsManager.OPTION_PIP_LAYOUT, - R.drawable.ic_pip_option_layout1); - public static final MenuAction PIP_SIZE_ACTION = - new MenuAction(R.string.pip_options_item_size, TvOptionsManager.OPTION_PIP_SIZE, - R.drawable.ic_pip_option_size); private final String mActionName; private final int mActionNameResId; - private final int mType; + @OptionType private final int mType; + private String mActionDescription; private Drawable mDrawable; private int mDrawableResId; private boolean mEnabled = true; + /** + * Sets the action description. Returns {@code trye} if the description is changed. + */ + public static boolean setActionDescription(MenuAction action, String actionDescription) { + String oldDescription = action.mActionDescription; + action.mActionDescription = actionDescription; + return !TextUtils.equals(action.mActionDescription, oldDescription); + } + + /** + * Enables or disables the action. Returns {@code true} if the value is changed. + */ + public static boolean setEnabled(MenuAction action, boolean enabled) { + boolean changed = action.mEnabled != enabled; + action.mEnabled = enabled; + return changed; + } + public MenuAction(int actionNameResId, int type, int drawableResId) { mActionName = null; mActionNameResId = actionNameResId; @@ -102,11 +101,11 @@ public class MenuAction { return context.getString(mActionNameResId); } - public String getActionDescription(Context context) { - return ((MainActivity) context).getTvOptionsManager().getOptionString(mType); + public String getActionDescription() { + return mActionDescription; } - public int getType() { + @OptionType public int getType() { return mType; } @@ -120,28 +119,10 @@ public class MenuAction { return mDrawable; } - /** - * Sets drawable resource id. - * - * @return {@code true} if drawable is changed. - */ - public boolean setDrawableResId(int resId) { - if (mDrawableResId == resId) { - return false; - } - mDrawable = null; - mDrawableResId = resId; - return true; - } - public boolean isEnabled() { return mEnabled; } - public void setEnabled(boolean enabled) { - mEnabled = enabled; - } - public int getActionNameResId() { return mActionNameResId; } diff --git a/src/com/android/tv/menu/MenuLayoutManager.java b/src/com/android/tv/menu/MenuLayoutManager.java index 6c767247..173d4004 100644 --- a/src/com/android/tv/menu/MenuLayoutManager.java +++ b/src/com/android/tv/menu/MenuLayoutManager.java @@ -28,6 +28,7 @@ import android.support.annotation.UiThread; import android.support.v4.view.animation.FastOutLinearInInterpolator; import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.support.v4.view.animation.LinearOutSlowInInterpolator; +import android.support.v7.widget.RecyclerView; import android.util.Log; import android.util.Property; import android.view.View; @@ -56,12 +57,14 @@ public class MenuLayoutManager { // The visible duration of the title before it is hidden. private static final long TITLE_SHOW_DURATION_BEFORE_HIDDEN_MS = TimeUnit.SECONDS.toMillis(2); + private static final int INVALID_POSITION = -1; private final MenuView mMenuView; private final List<MenuRow> mMenuRows = new ArrayList<>(); private final List<MenuRowView> mMenuRowViews = new ArrayList<>(); private final List<Integer> mRemovingRowViews = new ArrayList<>(); - private int mSelectedPosition = -1; + private int mSelectedPosition = INVALID_POSITION; + private int mPendingSelectedPosition = INVALID_POSITION; private final int mRowAlignFromBottom; private final int mRowContentsPaddingTop; @@ -130,8 +133,8 @@ public class MenuLayoutManager { MenuRowView currentView = mMenuRowViews.get(mSelectedPosition); if (currentView.getVisibility() == View.GONE) { // If the selected row is not visible, select the first visible row. - int firstVisiblePosition = findNextVisiblePosition(-1); - if (firstVisiblePosition != -1) { + int firstVisiblePosition = findNextVisiblePosition(INVALID_POSITION); + if (firstVisiblePosition != INVALID_POSITION) { mSelectedPosition = firstVisiblePosition; } else { // No rows are visible. @@ -157,6 +160,10 @@ public class MenuLayoutManager { view.onDeselected(); } } + + if (mPendingSelectedPosition != INVALID_POSITION) { + setSelectedPositionSmooth(mPendingSelectedPosition); + } } private int findNextVisiblePosition(int start) { @@ -166,7 +173,7 @@ public class MenuLayoutManager { return i; } } - return -1; + return INVALID_POSITION; } private void dumpChildren(String prefix) { @@ -327,6 +334,7 @@ public class MenuLayoutManager { mMenuRowViews.get(mSelectedPosition).onDeselected(); } mSelectedPosition = position; + mPendingSelectedPosition = INVALID_POSITION; if (Utils.isIndexValid(mMenuRowViews, mSelectedPosition)) { mMenuRowViews.get(mSelectedPosition).onSelected(false); } @@ -380,14 +388,29 @@ public class MenuLayoutManager { // again from the intermediate state. mTitleFadeOutAnimator.cancel(); } - final int oldPosition = mSelectedPosition; - mSelectedPosition = position; if (DEBUG) dumpChildren("startRowAnimation()"); - MenuRowView currentView = mMenuRowViews.get(position); // Show the children of the next row. - currentView.getTitleView().setVisibility(View.VISIBLE); - currentView.getContentsView().setVisibility(View.VISIBLE); + final MenuRowView currentView = mMenuRowViews.get(position); + TextView currentTitleView = currentView.getTitleView(); + View currentContentsView = currentView.getContentsView(); + currentTitleView.setVisibility(View.VISIBLE); + currentContentsView.setVisibility(View.VISIBLE); + if (currentView instanceof PlayControlsRowView) { + ((PlayControlsRowView) currentView).onPreselected(); + } + // When contents view's visibility is gone, layouting might be delayed until it's shown and + // thus cause onBindViewHolder() and menu action updating occurs in front of users' sight. + // Therefore we call requestLayout() here if there are pending adapter updates. + if (currentContentsView instanceof RecyclerView + && ((RecyclerView) currentContentsView).hasPendingAdapterUpdates()) { + currentContentsView.requestLayout(); + mPendingSelectedPosition = position; + return; + } + final int oldPosition = mSelectedPosition; + mSelectedPosition = position; + mPendingSelectedPosition = INVALID_POSITION; // Request focus after the new contents view shows up. mMenuView.requestFocus(); if (mTempTitleViewForOld == null) { @@ -407,7 +430,7 @@ public class MenuLayoutManager { // Old row. MenuRow oldRow = mMenuRows.get(oldPosition); - MenuRowView oldView = mMenuRowViews.get(oldPosition); + final MenuRowView oldView = mMenuRowViews.get(oldPosition); View oldContentsView = oldView.getContentsView(); // Old contents view. animators.add(createAlphaAnimator(oldContentsView, 1.0f, 0.0f, 1.0f, mLinearOutSlowIn) @@ -468,8 +491,6 @@ public class MenuLayoutManager { } // Current row. Rect currentLayoutRect = new Rect(layouts.get(position)); - TextView currentTitleView = currentView.getTitleView(); - View currentContentsView = currentView.getContentsView(); currentContentsView.setAlpha(0.0f); if (scrollDown) { // Current title view. @@ -529,7 +550,7 @@ public class MenuLayoutManager { int nextPosition; if (scrollDown) { nextPosition = findNextVisiblePosition(position); - if (nextPosition != -1) { + if (nextPosition != INVALID_POSITION) { MenuRowView nextView = mMenuRowViews.get(nextPosition); Rect nextLayoutRect = layouts.get(nextPosition); animators.add(createTranslationYAnimator(nextView, @@ -539,7 +560,7 @@ public class MenuLayoutManager { } } else { nextPosition = findNextVisiblePosition(oldPosition); - if (nextPosition != -1) { + if (nextPosition != INVALID_POSITION) { MenuRowView nextView = mMenuRowViews.get(nextPosition); animators.add(createTranslationYAnimator(nextView, 0, mRowScrollUpAnimationOffset)); animators.add(createAlphaAnimator(nextView, @@ -572,9 +593,8 @@ public class MenuLayoutManager { for (ViewPropertyValueHolder holder : propertyValuesAfterAnimation) { holder.property.set(holder.view, holder.value); } - oldTitleView.setVisibility(View.VISIBLE); - mMenuRowViews.get(oldPosition).onDeselected(); - mMenuRowViews.get(position).onSelected(true); + oldView.onDeselected(); + currentView.onSelected(true); mTempTitleViewForOld.setVisibility(View.GONE); mTempTitleViewForCurrent.setVisibility(View.GONE); layout(mMenuView.getLeft(), mMenuView.getTop(), mMenuView.getRight(), diff --git a/src/com/android/tv/menu/MenuRow.java b/src/com/android/tv/menu/MenuRow.java index 6f98e615..47804f11 100644 --- a/src/com/android/tv/menu/MenuRow.java +++ b/src/com/android/tv/menu/MenuRow.java @@ -123,11 +123,6 @@ public abstract class MenuRow { public void onRecentChannelsChanged() { } /** - * This method is called when stream information is changed. - */ - public void onStreamInfoChanged() { } - - /** * Returns whether to hide the title when the row is selected. */ public boolean hideTitleWhenSelected() { diff --git a/src/com/android/tv/menu/MenuRowFactory.java b/src/com/android/tv/menu/MenuRowFactory.java index c67a0e04..570cfb8f 100644 --- a/src/com/android/tv/menu/MenuRowFactory.java +++ b/src/com/android/tv/menu/MenuRowFactory.java @@ -67,8 +67,6 @@ public class MenuRowFactory { } else if (TvOptionsRow.class.equals(key)) { return new TvOptionsRow(mMainActivity, menu, mTvCustomizationManager .getCustomActions(TvCustomizationManager.ID_OPTIONS_ROW)); - } else if (PipOptionsRow.class.equals(key)) { - return new PipOptionsRow(mMainActivity, menu); } return null; } @@ -77,36 +75,15 @@ public class MenuRowFactory { * A menu row which represents the TV options row. */ public static class TvOptionsRow extends ItemListRow { + /** + * The ID of the row. + */ + public static final String ID = TvOptionsRow.class.getName(); + private TvOptionsRow(Context context, Menu menu, List<CustomAction> customActions) { super(context, menu, R.string.menu_title_options, R.dimen.action_card_height, new TvOptionsRowAdapter(context, customActions)); } - - @Override - public void onStreamInfoChanged() { - if (getMenu().isActive()) { - update(); - } - } - } - - /** - * A menu row which represents the PIP options row. - */ - public static class PipOptionsRow extends ItemListRow { - private final MainActivity mMainActivity; - - private PipOptionsRow(Context context, Menu menu) { - super(context, menu, R.string.menu_title_pip_options, R.dimen.action_card_height, - new PipOptionsRowAdapter(context)); - mMainActivity = (MainActivity) context; - } - - @Override - public boolean isVisible() { - // TODO: Remove the dependency on MainActivity. - return super.isVisible() && mMainActivity.isPipEnabled(); - } } /** diff --git a/src/com/android/tv/menu/MenuUpdater.java b/src/com/android/tv/menu/MenuUpdater.java index 075b299e..18416c85 100644 --- a/src/com/android/tv/menu/MenuUpdater.java +++ b/src/com/android/tv/menu/MenuUpdater.java @@ -16,11 +16,14 @@ package com.android.tv.menu; -import android.content.Context; import android.support.annotation.Nullable; import com.android.tv.ChannelTuner; +import com.android.tv.TvOptionsManager; +import com.android.tv.TvOptionsManager.OptionChangedListener; +import com.android.tv.TvOptionsManager.OptionType; import com.android.tv.data.Channel; +import com.android.tv.menu.MenuRowFactory.TvOptionsRow; import com.android.tv.ui.TunableTvView; import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener; @@ -30,10 +33,10 @@ import com.android.tv.ui.TunableTvView.OnScreenBlockingChangedListener; * <p>As the menu is updated when it shows up, this class handles only the dynamic updates. */ public class MenuUpdater { - // Can be null for testing. - @Nullable - private final TunableTvView mTvView; private final Menu mMenu; + // Can be null for testing. + @Nullable private final TunableTvView mTvView; + @Nullable private final TvOptionsManager mOptionsManager; private ChannelTuner mChannelTuner; private final ChannelTuner.Listener mChannelTunerListener = new ChannelTuner.Listener() { @@ -42,7 +45,7 @@ public class MenuUpdater { @Override public void onBrowsableChannelListChanged() { - mMenu.update(); + mMenu.update(ChannelsRow.ID); } @Override @@ -53,10 +56,17 @@ public class MenuUpdater { mMenu.update(ChannelsRow.ID); } }; + private final OptionChangedListener mOptionChangeListener = new OptionChangedListener() { + @Override + public void onOptionChanged(@OptionType int optionType, String newString) { + mMenu.update(TvOptionsRow.ID); + } + }; - public MenuUpdater(Context context, TunableTvView tvView, Menu menu) { - mTvView = tvView; + public MenuUpdater(Menu menu, TunableTvView tvView, TvOptionsManager optionsManager) { mMenu = menu; + mTvView = tvView; + mOptionsManager = optionsManager; if (mTvView != null) { mTvView.setOnScreenBlockedListener(new OnScreenBlockingChangedListener() { @Override @@ -65,11 +75,18 @@ public class MenuUpdater { } }); } + if (mOptionsManager != null) { + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS, + mOptionChangeListener); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE, + mOptionChangeListener); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO, + mOptionChangeListener); + } } /** - * Sets the instance of {@link ChannelTuner}. Call this method when the channel tuner is ready - * or not available any more. + * Sets the instance of {@link ChannelTuner}. Call this method when the channel tuner is ready. */ public void setChannelTuner(ChannelTuner channelTuner) { if (mChannelTuner != null) { @@ -79,7 +96,13 @@ public class MenuUpdater { if (mChannelTuner != null) { mChannelTuner.addListener(mChannelTunerListener); } - mMenu.update(); + } + + /** + * Called when the stream information changes. + */ + public void onStreamInfoChanged() { + mMenu.update(TvOptionsRow.ID); } /** @@ -92,5 +115,10 @@ public class MenuUpdater { if (mTvView != null) { mTvView.setOnScreenBlockedListener(null); } + if (mOptionsManager != null) { + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_CLOSED_CAPTIONS, null); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_DISPLAY_MODE, null); + mOptionsManager.setOptionChangedListener(TvOptionsManager.OPTION_MULTI_AUDIO, null); + } } } diff --git a/src/com/android/tv/menu/OptionsRowAdapter.java b/src/com/android/tv/menu/OptionsRowAdapter.java index 93bd0a4d..dd6194a1 100644 --- a/src/com/android/tv/menu/OptionsRowAdapter.java +++ b/src/com/android/tv/menu/OptionsRowAdapter.java @@ -21,8 +21,6 @@ import android.view.View; import com.android.tv.R; import com.android.tv.TvApplication; -import com.android.tv.TvOptionsManager; -import com.android.tv.TvOptionsManager.OptionChangedListener; import com.android.tv.analytics.Tracker; import java.util.List; @@ -66,12 +64,9 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< public void update() { if (mActionList == null) { mActionList = createActions(); - updateActions(); setItemList(mActionList); } else { - if (updateActions()) { - setItemList(mActionList); - } + updateActions(); } } @@ -81,7 +76,7 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< } protected abstract List<MenuAction> createActions(); - protected abstract boolean updateActions(); + protected abstract void updateActions(); protected abstract void executeAction(int type); /** @@ -93,37 +88,6 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< return mActionList.get(position); } - /** - * Sets the action at the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} - */ - protected void setAction(int position, MenuAction action) { - mActionList.set(position, action); - } - - /** - * Adds an action to the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} - */ - protected void addAction(int position, MenuAction action) { - mActionList.add(position, action); - } - - /** - * Removes an action at the given position. - * Note that action at the position may differ from returned by {@link #createActions}. - * See {@link CustomizableOptionsRowAdapter} - */ - protected void removeAction(int position) { - mActionList.remove(position); - } - - protected int getActionSize() { - return mActionList.size(); - } - @Override public void onBindViewHolder(MyViewHolder viewHolder, int position) { super.onBindViewHolder(viewHolder, position); @@ -139,14 +103,4 @@ public abstract class OptionsRowAdapter extends ItemListRowView.ItemListAdapter< // be preserved. return mActionList.get(position).getType(); } - - protected void setOptionChangedListener(final MenuAction action) { - TvOptionsManager om = getMainActivity().getTvOptionsManager(); - om.setOptionChangedListener(action.getType(), new OptionChangedListener() { - @Override - public void onOptionChanged(String newOption) { - setItemList(mActionList); - } - }); - } } diff --git a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java index f3e09f80..c8249a4c 100644 --- a/src/com/android/tv/menu/PartnerOptionsRowAdapter.java +++ b/src/com/android/tv/menu/PartnerOptionsRowAdapter.java @@ -38,8 +38,7 @@ public class PartnerOptionsRowAdapter extends CustomizableOptionsRowAdapter { } @Override - protected boolean updateActions() { + protected void updateActions() { // TODO: Support adding description for custom actions. - return false; } } diff --git a/src/com/android/tv/menu/PipOptionsRowAdapter.java b/src/com/android/tv/menu/PipOptionsRowAdapter.java deleted file mode 100644 index 87203e9d..00000000 --- a/src/com/android/tv/menu/PipOptionsRowAdapter.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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.menu; - -import android.content.Context; -import android.text.TextUtils; - -import com.android.tv.MainActivity; -import com.android.tv.R; -import com.android.tv.TvOptionsManager; -import com.android.tv.ui.TvViewUiManager; -import com.android.tv.ui.sidepanel.PipInputSelectorFragment; -import com.android.tv.util.PipInputManager.PipInput; -import com.android.tv.util.TvSettings; - -import java.util.ArrayList; -import java.util.List; - -/* - * An adapter of PIP options. - */ -public class PipOptionsRowAdapter extends OptionsRowAdapter { - private static final int[] DRAWABLE_ID_FOR_LAYOUT = { - R.drawable.ic_pip_option_layout1, - R.drawable.ic_pip_option_layout2, - R.drawable.ic_pip_option_layout3, - R.drawable.ic_pip_option_layout4, - R.drawable.ic_pip_option_layout5 }; - - private final TvOptionsManager mTvOptionsManager; - private final TvViewUiManager mTvViewUiManager; - - public PipOptionsRowAdapter(Context context) { - super(context); - mTvOptionsManager = getMainActivity().getTvOptionsManager(); - mTvViewUiManager = getMainActivity().getTvViewUiManager(); - } - - @Override - protected List<MenuAction> createActions() { - List<MenuAction> actionList = new ArrayList<>(); - actionList.add(MenuAction.PIP_SELECT_INPUT_ACTION); - actionList.add(MenuAction.PIP_SWAP_ACTION); - actionList.add(MenuAction.PIP_SOUND_ACTION); - actionList.add(MenuAction.PIP_LAYOUT_ACTION); - actionList.add(MenuAction.PIP_SIZE_ACTION); - for (MenuAction action : actionList) { - setOptionChangedListener(action); - } - return actionList; - } - - @Override - public boolean updateActions() { - boolean changed = false; - if (updateSelectInputAction()) { - changed = true; - } - if (updateLayoutAction()) { - changed = true; - } - if (updateSizeAction()) { - changed = true; - } - return changed; - } - - private boolean updateSelectInputAction() { - String oldInputLabel = mTvOptionsManager.getOptionString(TvOptionsManager.OPTION_PIP_INPUT); - - MainActivity tvActivity = getMainActivity(); - PipInput newInput = tvActivity.getPipInputManager().getPipInput(tvActivity.getPipChannel()); - String newInputLabel = newInput == null ? null : newInput.getLabel(); - - if (!TextUtils.equals(oldInputLabel, newInputLabel)) { - mTvOptionsManager.onPipInputChanged(newInputLabel); - return true; - } - return false; - } - - private boolean updateLayoutAction() { - return MenuAction.PIP_LAYOUT_ACTION.setDrawableResId( - DRAWABLE_ID_FOR_LAYOUT[mTvViewUiManager.getPipLayout()]); - } - - private boolean updateSizeAction() { - boolean oldEnabled = MenuAction.PIP_SIZE_ACTION.isEnabled(); - boolean newEnabled = mTvViewUiManager.getPipLayout() != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE; - if (oldEnabled != newEnabled) { - MenuAction.PIP_SIZE_ACTION.setEnabled(newEnabled); - return true; - } - return false; - } - - @Override - protected void executeAction(int type) { - switch (type) { - case TvOptionsManager.OPTION_PIP_INPUT: - getMainActivity().getOverlayManager().getSideFragmentManager().show( - new PipInputSelectorFragment()); - break; - case TvOptionsManager.OPTION_PIP_SWAP: - getMainActivity().swapPip(); - break; - case TvOptionsManager.OPTION_PIP_SOUND: - getMainActivity().togglePipSoundMode(); - break; - case TvOptionsManager.OPTION_PIP_LAYOUT: - int oldLayout = mTvViewUiManager.getPipLayout(); - int newLayout = (oldLayout + 1) % (TvSettings.PIP_LAYOUT_LAST + 1); - mTvViewUiManager.setPipLayout(newLayout, true); - MenuAction.PIP_LAYOUT_ACTION.setDrawableResId(DRAWABLE_ID_FOR_LAYOUT[newLayout]); - break; - case TvOptionsManager.OPTION_PIP_SIZE: - int oldSize = mTvViewUiManager.getPipSize(); - int newSize = (oldSize + 1) % (TvSettings.PIP_SIZE_LAST + 1); - mTvViewUiManager.setPipSize(newSize, true); - break; - } - } -} diff --git a/src/com/android/tv/menu/PlayControlsButton.java b/src/com/android/tv/menu/PlayControlsButton.java index aff39db3..77715f28 100644 --- a/src/com/android/tv/menu/PlayControlsButton.java +++ b/src/com/android/tv/menu/PlayControlsButton.java @@ -39,6 +39,9 @@ public class PlayControlsButton extends FrameLayout { private final int mIconColor; private int mIconFocusedColor; + private int mImageResourceId; + private int mTintColor; + public PlayControlsButton(Context context) { this(context, null); } @@ -67,10 +70,21 @@ public class PlayControlsButton extends FrameLayout { * Sets the resource ID of the image to be displayed in the center of this control. */ public void setImageResId(int imageResId) { - mIcon.setImageResource(imageResId); - // Since on foucus changing, icons' color should be switched with animation, + int newTintColor = hasFocus() ? mIconFocusedColor : mIconColor; + if (mImageResourceId != imageResId) { + mImageResourceId = imageResId; + mIcon.setImageResource(imageResId); + updateTint(newTintColor); + } else if (newTintColor != mTintColor) { + updateTint(newTintColor); + } + } + + private void updateTint(int tintColor) { + mTintColor = tintColor; + // Since on focus changing, icons' color should be switched with animation, // as a result, selectors cannot be used to switch colors in this case. - mIcon.getDrawable().setTint(hasFocus() ? mIconFocusedColor : mIconColor); + mIcon.getDrawable().setTint(tintColor); } /** @@ -117,7 +131,9 @@ public class PlayControlsButton extends FrameLayout { } else { mIcon.setVisibility(View.GONE); mLabel.setVisibility(View.VISIBLE); - mLabel.setText(label); + if (!TextUtils.equals(mLabel.getText(), label)) { + mLabel.setText(label); + } } } diff --git a/src/com/android/tv/menu/PlayControlsRowView.java b/src/com/android/tv/menu/PlayControlsRowView.java index a620d4dd..4d766788 100644 --- a/src/com/android/tv/menu/PlayControlsRowView.java +++ b/src/com/android/tv/menu/PlayControlsRowView.java @@ -18,10 +18,10 @@ package com.android.tv.menu; import android.content.Context; import android.content.res.Resources; +import android.text.TextUtils; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.View; -import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; @@ -34,17 +34,16 @@ import com.android.tv.common.SoftPreconditions; import com.android.tv.common.feature.CommonFeatures; import com.android.tv.data.Channel; import com.android.tv.data.Program; +import com.android.tv.dialog.HalfSizedDialogFragment; import com.android.tv.dvr.DvrDataManager; import com.android.tv.dvr.DvrDataManager.OnDvrScheduleLoadFinishedListener; import com.android.tv.dvr.DvrDataManager.ScheduledRecordingListener; import com.android.tv.dvr.DvrManager; -import com.android.tv.dvr.DvrUiHelper; -import com.android.tv.dvr.ScheduledRecording; +import com.android.tv.dvr.data.ScheduledRecording; import com.android.tv.dvr.ui.DvrStopRecordingFragment; -import com.android.tv.dvr.ui.HalfSizedDialogFragment; +import com.android.tv.dvr.ui.DvrUiHelper; import com.android.tv.menu.Menu.MenuShowReason; import com.android.tv.ui.TunableTvView; -import com.android.tv.util.Utils; public class PlayControlsRowView extends MenuRowView { private static final int NORMAL_WIDTH_MAX_BUTTON_COUNT = 5; @@ -53,14 +52,10 @@ public class PlayControlsRowView extends MenuRowView { private final int mTimeTextLeftMargin; private final int mTimelineWidth; // Views - private View mBackgroundView; + private TextView mBackgroundView; private View mTimeIndicator; private TextView mTimeText; - private View mProgressEmptyBefore; - private View mProgressWatched; - private View mProgressBuffered; - private View mProgressEmptyAfter; - private View mControlBar; + private PlaybackProgressBar mProgress; private PlayControlsButton mJumpPreviousButton; private PlayControlsButton mRewindButton; private PlayControlsButton mPlayPauseButton; @@ -69,7 +64,6 @@ public class PlayControlsRowView extends MenuRowView { private PlayControlsButton mRecordButton; private TextView mProgramStartTimeText; private TextView mProgramEndTimeText; - private View mUnavailableMessageText; private TunableTvView mTvView; private TimeShiftManager mTimeShiftManager; private final DvrDataManager mDvrDataManager; @@ -83,6 +77,8 @@ public class PlayControlsRowView extends MenuRowView { private final int mNormalButtonMargin; private final int mCompactButtonMargin; + private final String mUnavailableMessage; + private final ScheduledRecordingListener mScheduledRecordingListener = new ScheduledRecordingListener() { @Override @@ -138,6 +134,7 @@ public class PlayControlsRowView extends MenuRowView { mDvrManager = null; } mMainActivity = (MainActivity) context; + mUnavailableMessage = res.getString(R.string.play_controls_unavailable); } @Override @@ -171,14 +168,10 @@ public class PlayControlsRowView extends MenuRowView { super.onFinishInflate(); // Clip the ViewGroup(body) to the rounded rectangle of outline. findViewById(R.id.body).setClipToOutline(true); - mBackgroundView = findViewById(R.id.background); + mBackgroundView = (TextView) findViewById(R.id.background); mTimeIndicator = findViewById(R.id.time_indicator); mTimeText = (TextView) findViewById(R.id.time_text); - mProgressEmptyBefore = findViewById(R.id.timeline_bg_start); - mProgressWatched = findViewById(R.id.watched); - mProgressBuffered = findViewById(R.id.buffered); - mProgressEmptyAfter = findViewById(R.id.timeline_bg_end); - mControlBar = findViewById(R.id.play_control_bar); + mProgress = (PlaybackProgressBar) findViewById(R.id.progress); mJumpPreviousButton = (PlayControlsButton) findViewById(R.id.jump_previous); mRewindButton = (PlayControlsButton) findViewById(R.id.rewind); mPlayPauseButton = (PlayControlsButton) findViewById(R.id.play_pause); @@ -187,7 +180,6 @@ public class PlayControlsRowView extends MenuRowView { mRecordButton = (PlayControlsButton) findViewById(R.id.record); mProgramStartTimeText = (TextView) findViewById(R.id.program_start_time); mProgramEndTimeText = (TextView) findViewById(R.id.program_end_time); - mUnavailableMessageText = findViewById(R.id.unavailable_text); initializeButton(mJumpPreviousButton, R.drawable.lb_ic_skip_previous, R.string.play_controls_description_skip_previous, null, new Runnable() { @@ -195,7 +187,7 @@ public class PlayControlsRowView extends MenuRowView { public void run() { if (mTimeShiftManager.isAvailable()) { mTimeShiftManager.jumpToPrevious(); - updateControls(); + updateControls(true); } } }); @@ -235,7 +227,7 @@ public class PlayControlsRowView extends MenuRowView { public void run() { if (mTimeShiftManager.isAvailable()) { mTimeShiftManager.jumpToNext(); - updateControls(); + updateControls(true); } } }); @@ -265,18 +257,17 @@ public class PlayControlsRowView extends MenuRowView { if (!(mDvrManager != null && mDvrManager.isChannelRecordable(currentChannel))) { Toast.makeText(mMainActivity, R.string.dvr_msg_cannot_record_channel, Toast.LENGTH_SHORT).show(); - } else if (DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity, - currentChannel.getInputId())) { + } else { Program program = TvApplication.getSingletons(mMainActivity).getProgramDataManager() .getCurrentProgram(currentChannel.getId()); - if (program == null) { - DvrUiHelper.showChannelRecordDurationOptions(mMainActivity, currentChannel); - } else if (DvrUiHelper.handleCreateSchedule(mMainActivity, program)) { - String msg = mMainActivity.getString(R.string.dvr_msg_current_program_scheduled, - program.getTitle(), - Utils.toTimeString(program.getEndTimeUtcMillis(), false)); - Toast.makeText(mMainActivity, msg, Toast.LENGTH_SHORT).show(); - } + DvrUiHelper.checkStorageStatusAndShowErrorMessage(mMainActivity, + currentChannel.getInputId(), new Runnable() { + @Override + public void run() { + DvrUiHelper.requestRecordingCurrentProgram(mMainActivity, + currentChannel, program, true); + } + }); } } else if (currentChannel != null) { DvrUiHelper.showStopRecordingDialog(mMainActivity, currentChannel.getId(), @@ -318,39 +309,37 @@ public class PlayControlsRowView extends MenuRowView { @Override public void onAvailabilityChanged() { updateMenuVisibility(); - if (isShown()) { - PlayControlsRowView.this.updateAll(); - } + PlayControlsRowView.this.updateAll(false); } @Override public void onPlayStatusChanged(int status) { updateMenuVisibility(); - if (mTimeShiftManager.isAvailable() && isShown()) { - updateControls(); + if (mTimeShiftManager.isAvailable()) { + updateControls(false); } } @Override public void onRecordTimeRangeChanged() { - if (mTimeShiftManager.isAvailable() && isShown()) { - updateControls(); + if (mTimeShiftManager.isAvailable()) { + updateControls(false); } } @Override public void onCurrentPositionChanged() { - if (mTimeShiftManager.isAvailable() && isShown()) { + if (mTimeShiftManager.isAvailable()) { initializeTimeline(); - updateControls(); + updateControls(false); } } @Override public void onProgramInfoChanged() { - if (mTimeShiftManager.isAvailable() && isShown()) { + if (mTimeShiftManager.isAvailable()) { initializeTimeline(); - updateControls(); + updateControls(false); } } @@ -372,7 +361,8 @@ public class PlayControlsRowView extends MenuRowView { } } }); - updateAll(); + // force update to initialize everything + updateAll(true); } private void initializeTimeline() { @@ -380,6 +370,8 @@ public class PlayControlsRowView extends MenuRowView { mTimeShiftManager.getCurrentPositionMs()); mProgramStartTimeMs = program.getStartTimeUtcMillis(); mProgramEndTimeMs = program.getEndTimeUtcMillis(); + mProgress.setMax(mProgramEndTimeMs - mProgramStartTimeMs); + updateRecTimeText(); SoftPreconditions.checkArgument(mProgramStartTimeMs <= mProgramEndTimeMs); } @@ -389,10 +381,13 @@ public class PlayControlsRowView extends MenuRowView { getMenu().setKeepVisible(keepMenuVisible); } + public void onPreselected() { + updateControls(true); + } + @Override public void onSelected(boolean showTitle) { super.onSelected(showTitle); - updateControls(); postHideRippleAnimation(); } @@ -474,28 +469,32 @@ public class PlayControlsRowView extends MenuRowView { * Updates the view contents. It is called from the PlayControlsRow. */ public void update() { - updateAll(); + updateAll(false); } - private void updateAll() { + private void updateAll(boolean forceUpdate) { if (mTimeShiftManager.isAvailable() && !mTvView.isScreenBlocked()) { setEnabled(true); initializeTimeline(); mBackgroundView.setEnabled(true); + setTextIfNeeded(mBackgroundView, null); } else { setEnabled(false); mBackgroundView.setEnabled(false); + setTextIfNeeded(mBackgroundView, mUnavailableMessage); } - updateControls(); + // force the controls be updated no matter it's visible or not. + updateControls(forceUpdate); } - private void updateControls() { - updateTime(); - updateProgress(); - updateRecTimeText(); - updateButtons(); - updateRecordButton(); - updateButtonMargin(); + private void updateControls(boolean forceUpdate) { + if (forceUpdate || getContentsView().isShown()) { + updateTime(); + updateProgress(); + updateButtons(); + updateRecordButton(); + updateButtonMargin(); + } } private void updateTime() { @@ -504,70 +503,39 @@ public class PlayControlsRowView extends MenuRowView { mTimeIndicator.setVisibility(View.VISIBLE); } else { mTimeText.setVisibility(View.INVISIBLE); - mTimeIndicator.setVisibility(View.INVISIBLE); + mTimeIndicator.setVisibility(View.GONE); return; } long currentPositionMs = mTimeShiftManager.getCurrentPositionMs(); - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) mTimeText.getLayoutParams(); int currentTimePositionPixel = convertDurationToPixel(currentPositionMs - mProgramStartTimeMs); - params.leftMargin = currentTimePositionPixel + mTimeTextLeftMargin; - mTimeText.setLayoutParams(params); - mTimeText.setText(getTimeString(currentPositionMs)); - params = (ViewGroup.MarginLayoutParams) mTimeIndicator.getLayoutParams(); - params.leftMargin = currentTimePositionPixel + mTimeIndicatorLeftMargin; - mTimeIndicator.setLayoutParams(params); + mTimeText.setTranslationX(currentTimePositionPixel + mTimeTextLeftMargin); + setTextIfNeeded(mTimeText, getTimeString(currentPositionMs)); + mTimeIndicator.setTranslationX(currentTimePositionPixel + mTimeIndicatorLeftMargin); } private void updateProgress() { if (isEnabled()) { - mProgressWatched.setVisibility(View.VISIBLE); - mProgressBuffered.setVisibility(View.VISIBLE); - mProgressEmptyAfter.setVisibility(View.VISIBLE); - } else { - mProgressWatched.setVisibility(View.INVISIBLE); - mProgressBuffered.setVisibility(View.INVISIBLE); - mProgressEmptyAfter.setVisibility(View.INVISIBLE); - if (mProgramStartTimeMs < mProgramEndTimeMs) { - layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, mProgramEndTimeMs); - } else { - // Not initialized yet. - layoutProgress(mProgressEmptyBefore, mTimelineWidth); - } - return; - } - - long progressStartTimeMs = Math.min(mProgramEndTimeMs, + long progressStartTimeMs = Math.min(mProgramEndTimeMs, Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordStartTimeMs())); - long currentPlayingTimeMs = Math.min(mProgramEndTimeMs, + long currentPlayingTimeMs = Math.min(mProgramEndTimeMs, Math.max(mProgramStartTimeMs, mTimeShiftManager.getCurrentPositionMs())); - long progressEndTimeMs = Math.min(mProgramEndTimeMs, + long progressEndTimeMs = Math.min(mProgramEndTimeMs, Math.max(mProgramStartTimeMs, mTimeShiftManager.getRecordEndTimeMs())); - - layoutProgress(mProgressEmptyBefore, mProgramStartTimeMs, progressStartTimeMs); - layoutProgress(mProgressWatched, progressStartTimeMs, currentPlayingTimeMs); - layoutProgress(mProgressBuffered, currentPlayingTimeMs, progressEndTimeMs); - } - - private void layoutProgress(View progress, long progressStartTimeMs, long progressEndTimeMs) { - layoutProgress(progress, Math.max(0, - convertDurationToPixel(progressEndTimeMs - progressStartTimeMs)) + 1); - } - - private void layoutProgress(View progress, int width) { - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) progress.getLayoutParams(); - params.width = width; - progress.setLayoutParams(params); + mProgress.setProgressRange(progressStartTimeMs - mProgramStartTimeMs, + progressEndTimeMs - mProgramStartTimeMs); + mProgress.setProgress(currentPlayingTimeMs - mProgramStartTimeMs); + } else { + mProgress.setProgressRange(0, 0); + } } private void updateRecTimeText() { if (isEnabled()) { mProgramStartTimeText.setVisibility(View.VISIBLE); - mProgramStartTimeText.setText(getTimeString(mProgramStartTimeMs)); + setTextIfNeeded(mProgramStartTimeText, getTimeString(mProgramStartTimeMs)); mProgramEndTimeText.setVisibility(View.VISIBLE); - mProgramEndTimeText.setText(getTimeString(mProgramEndTimeMs)); + setTextIfNeeded(mProgramEndTimeText, getTimeString(mProgramEndTimeMs)); } else { mProgramStartTimeText.setVisibility(View.GONE); mProgramEndTimeText.setVisibility(View.GONE); @@ -576,11 +544,17 @@ public class PlayControlsRowView extends MenuRowView { private void updateButtons() { if (isEnabled()) { - mControlBar.setVisibility(View.VISIBLE); - mUnavailableMessageText.setVisibility(View.GONE); + mPlayPauseButton.setVisibility(View.VISIBLE); + mJumpPreviousButton.setVisibility(View.VISIBLE); + mJumpNextButton.setVisibility(View.VISIBLE); + mRewindButton.setVisibility(View.VISIBLE); + mFastForwardButton.setVisibility(View.VISIBLE); } else { - mControlBar.setVisibility(View.INVISIBLE); - mUnavailableMessageText.setVisibility(View.VISIBLE); + mPlayPauseButton.setVisibility(View.GONE); + mJumpPreviousButton.setVisibility(View.GONE); + mJumpNextButton.setVisibility(View.GONE); + mRewindButton.setVisibility(View.GONE); + mFastForwardButton.setVisibility(View.GONE); return; } @@ -622,6 +596,12 @@ public class PlayControlsRowView extends MenuRowView { } private void updateRecordButton() { + if (isEnabled()) { + mRecordButton.setVisibility(VISIBLE); + } else { + mRecordButton.setVisibility(GONE); + return; + } if (!(mDvrManager != null && mDvrManager.isChannelRecordable(mMainActivity.getCurrentChannel()))) { mRecordButton.setVisibility(View.GONE); @@ -682,4 +662,10 @@ public class PlayControlsRowView extends MenuRowView { mDvrDataManager.removeScheduledRecordingListener(mScheduledRecordingListener); } } + + private void setTextIfNeeded(TextView textView, String text) { + if (!TextUtils.equals(textView.getText(), text)) { + textView.setText(text); + } + } } diff --git a/src/com/android/tv/menu/PlaybackProgressBar.java b/src/com/android/tv/menu/PlaybackProgressBar.java new file mode 100644 index 00000000..e8061bc6 --- /dev/null +++ b/src/com/android/tv/menu/PlaybackProgressBar.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 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.menu; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.util.AttributeSet; +import android.view.View; + +import com.android.tv.R; + +/** + * A progress bar control which has two progresses which start in the middle of the control. + */ +public class PlaybackProgressBar extends View { + private final LayerDrawable mProgressDrawable; + private final Drawable mPrimaryDrawable; + private final Drawable mSecondaryDrawable; + private long mMax = 100; + private long mProgressStart = 0; + private long mProgressEnd = 0; + private long mProgress = 0; + + public PlaybackProgressBar(Context context) { + this(context, null); + } + + public PlaybackProgressBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public PlaybackProgressBar(Context context, AttributeSet attrs, int defStyleAttr, + int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + TypedArray a = context.obtainStyledAttributes( + attrs, R.styleable.PlaybackProgressBar, defStyleAttr, defStyleRes); + mProgressDrawable = + (LayerDrawable) a.getDrawable(R.styleable.PlaybackProgressBar_progressDrawable); + mPrimaryDrawable = mProgressDrawable.findDrawableByLayerId(android.R.id.progress); + mSecondaryDrawable = + mProgressDrawable.findDrawableByLayerId(android.R.id.secondaryProgress); + a.recycle(); + refreshProgress(); + } + + @Override + protected void onDraw(Canvas canvas) { + final int saveCount = canvas.save(); + canvas.translate(getPaddingLeft(), getPaddingTop()); + mProgressDrawable.draw(canvas); + canvas.restoreToCount(saveCount); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + refreshProgress(); + } + + public void setMax(long max) { + if (max < 0) { + max = 0; + } + if (max != mMax) { + mMax = max; + if (mProgressStart > max) { + mProgressStart = max; + } + if (mProgressEnd > max) { + mProgressEnd = max; + } + if (mProgress > max) { + mProgress = max; + } + refreshProgress(); + } + } + + /** + * Sets the start and end position of the progress. + */ + public void setProgressRange(long start, long end) { + start = constrain(start, 0, mMax); + end = constrain(end, start, mMax); + mProgress = constrain(mProgress, start, end); + if (start != mProgressStart || end != mProgressEnd) { + mProgressStart = start; + mProgressEnd = end; + setProgressLevels(); + } + } + + /** + * Sets the progress position. + */ + public void setProgress(long progress) { + progress = constrain(progress, mProgressStart, mProgressEnd); + if (progress != mProgress) { + mProgress = progress; + setProgressLevels(); + } + } + + private long constrain(long value, long min, long max) { + return Math.min(Math.max(value, min), max); + } + + private void refreshProgress() { + int width = getWidth() - getPaddingStart() - getPaddingEnd(); + int height = getHeight() - getPaddingTop() - getPaddingBottom(); + mProgressDrawable.setBounds(0, 0, width, height); + setProgressLevels(); + } + + private void setProgressLevels() { + boolean progressUpdated = setProgressBound(mPrimaryDrawable, mProgressStart, mProgress); + progressUpdated |= setProgressBound(mSecondaryDrawable, mProgress, mProgressEnd); + if (progressUpdated) { + postInvalidate(); + } + } + + private boolean setProgressBound(Drawable drawable, long start, long end) { + Rect oldBounds = drawable.getBounds(); + if (mMax == 0) { + if (!isEqualRect(oldBounds, 0, 0, 0, 0)) { + drawable.setBounds(0, 0, 0, 0); + return true; + } + return false; + } + int width = mProgressDrawable.getBounds().width(); + int height = mProgressDrawable.getBounds().height(); + int left = (int) (width * start / mMax); + int right = (int) (width * end / mMax); + if (!isEqualRect(oldBounds, left, 0, right, height)) { + drawable.setBounds(left, 0, right, height); + return true; + } + return false; + } + + private boolean isEqualRect(Rect rect, int left, int top, int right, int bottom) { + return rect.left == left && rect.top == top && rect.right == right && rect.bottom == bottom; + } +} diff --git a/src/com/android/tv/menu/SimpleCardView.java b/src/com/android/tv/menu/SimpleCardView.java index c99834be..fc5192da 100644 --- a/src/com/android/tv/menu/SimpleCardView.java +++ b/src/com/android/tv/menu/SimpleCardView.java @@ -19,12 +19,10 @@ package com.android.tv.menu; import android.content.Context; import android.util.AttributeSet; -import com.android.tv.data.Channel; - /** * A view to render a guide card. */ -public class SimpleCardView extends BaseCardView<Channel> { +public class SimpleCardView extends BaseCardView<ChannelsRowItem> { public SimpleCardView(Context context) { this(context, null, 0); diff --git a/src/com/android/tv/menu/TvOptionsRowAdapter.java b/src/com/android/tv/menu/TvOptionsRowAdapter.java index fb062246..6e035f22 100644 --- a/src/com/android/tv/menu/TvOptionsRowAdapter.java +++ b/src/com/android/tv/menu/TvOptionsRowAdapter.java @@ -21,7 +21,6 @@ import android.media.tv.TvTrackInfo; import android.support.annotation.VisibleForTesting; import com.android.tv.Features; -import com.android.tv.R; import com.android.tv.TvOptionsManager; import com.android.tv.customization.CustomAction; import com.android.tv.data.DisplayMode; @@ -30,7 +29,7 @@ import com.android.tv.ui.sidepanel.ClosedCaptionFragment; import com.android.tv.ui.sidepanel.DeveloperOptionFragment; import com.android.tv.ui.sidepanel.DisplayModeFragment; import com.android.tv.ui.sidepanel.MultiAudioFragment; -import com.android.tv.util.PipInputManager; +import com.android.tv.util.Utils; import java.util.ArrayList; import java.util.List; @@ -39,12 +38,6 @@ import java.util.List; * An adapter of options. */ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { - private static final boolean ENABLE_IN_APP_PIP = false; - - private int mPositionPipAction; - // If mInAppPipAction is false, system-wide PIP is used. - private boolean mInAppPipAction = true; - public TvOptionsRowAdapter(Context context, List<CustomAction> customActions) { super(context, customActions); } @@ -53,123 +46,73 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { protected List<MenuAction> createBaseActions() { List<MenuAction> actionList = new ArrayList<>(); actionList.add(MenuAction.SELECT_CLOSED_CAPTION_ACTION); - setOptionChangedListener(MenuAction.SELECT_CLOSED_CAPTION_ACTION); actionList.add(MenuAction.SELECT_DISPLAY_MODE_ACTION); - setOptionChangedListener(MenuAction.SELECT_DISPLAY_MODE_ACTION); - actionList.add(MenuAction.PIP_IN_APP_ACTION); - setOptionChangedListener(MenuAction.PIP_IN_APP_ACTION); - mPositionPipAction = actionList.size() - 1; + if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) { + actionList.add(MenuAction.SYSTEMWIDE_PIP_ACTION); + } actionList.add(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); - setOptionChangedListener(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); actionList.add(MenuAction.MORE_CHANNELS_ACTION); - if (DeveloperOptionFragment.shouldShow()) { + if (Utils.isDeveloper()) { actionList.add(MenuAction.DEV_ACTION); } actionList.add(MenuAction.SETTINGS_ACTION); - if (getCustomActions() != null) { - // Adjust Pip action position which will be changed by applying custom actions. - for (CustomAction customAction : getCustomActions()) { - if (customAction.isFront()) { - mPositionPipAction++; - } - } - } - + updateClosedCaptionAction(); + updatePipAction(); + updateMultiAudioAction(); + updateDisplayModeAction(); return actionList; } @Override - protected boolean updateActions() { - boolean changed = false; + protected void updateActions() { + if (updateClosedCaptionAction()) { + notifyItemChanged(getItemPosition(MenuAction.SELECT_CLOSED_CAPTION_ACTION)); + } if (updatePipAction()) { - changed = true; + notifyItemChanged(getItemPosition(MenuAction.SYSTEMWIDE_PIP_ACTION)); } if (updateMultiAudioAction()) { - changed = true; + notifyItemChanged(getItemPosition(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION)); } if (updateDisplayModeAction()) { - changed = true; + notifyItemChanged(getItemPosition(MenuAction.SELECT_DISPLAY_MODE_ACTION)); } - return changed; } - private boolean updatePipAction() { - // There are four states. - // Case 1. The device doesn't even have any input for PIP. (e.g. OTT box without HDMI input) - // => Remove the icon. - // Case 2. The device has one or more inputs for PIP but none of them are currently - // available. - // => Show the icon but disable it. - // Case 3. The device has one or more available PIP inputs and now it's tuned off. - // => Show the icon with "Off". - // Case 4. The device has one or more available PIP inputs but it's already turned on. - // => Show the icon with "On". - - boolean changed = false; - - // Case 1 - PipInputManager pipInputManager = getMainActivity().getPipInputManager(); - if (ENABLE_IN_APP_PIP && pipInputManager.getPipInputSize(false) > 1) { - if (!mInAppPipAction) { - removeAction(mPositionPipAction); - addAction(mPositionPipAction, MenuAction.PIP_IN_APP_ACTION); - mInAppPipAction = true; - changed = true; - } - } else { - if (mInAppPipAction) { - removeAction(mPositionPipAction); - mInAppPipAction = false; - if (Features.PICTURE_IN_PICTURE.isEnabled(getMainActivity())) { - addAction(mPositionPipAction, MenuAction.SYSTEMWIDE_PIP_ACTION); - } - return true; - } - return false; - } + @VisibleForTesting + private boolean updateClosedCaptionAction() { + return updateActionDescription(MenuAction.SELECT_CLOSED_CAPTION_ACTION); + } - // Case 2 - boolean isPipEnabled = getMainActivity().isPipEnabled(); - boolean oldEnabled = MenuAction.PIP_IN_APP_ACTION.isEnabled(); - boolean newEnabled = pipInputManager.getPipInputSize(true) > 0; - if (oldEnabled != newEnabled) { - // Should not disable the item if the PIP is already turned on so that the user can - // force exit it. - if (newEnabled || !isPipEnabled) { - MenuAction.PIP_IN_APP_ACTION.setEnabled(newEnabled); - changed = true; - } + private boolean updatePipAction() { + if (containsItem(MenuAction.SYSTEMWIDE_PIP_ACTION)) { + return MenuAction.setEnabled(MenuAction.SYSTEMWIDE_PIP_ACTION, + !getMainActivity().isScreenBlockedByResourceConflictOrParentalControl()); } - - // Case 3 & 4 - we just need to update the icon. - MenuAction.PIP_IN_APP_ACTION.setDrawableResId( - isPipEnabled ? R.drawable.ic_tvoption_pip : R.drawable.ic_tvoption_pip_off); - return changed; + return false; } - @VisibleForTesting boolean updateMultiAudioAction() { List<TvTrackInfo> audioTracks = getMainActivity().getTracks(TvTrackInfo.TYPE_AUDIO); - boolean oldEnabled = MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.isEnabled(); - boolean newEnabled = audioTracks != null && audioTracks.size() > 1; - if (oldEnabled != newEnabled) { - MenuAction.SELECT_AUDIO_LANGUAGE_ACTION.setEnabled(newEnabled); - return true; - } - return false; + boolean enabled = audioTracks != null && audioTracks.size() > 1; + // Use "|" operator for non-short-circuit evaluation. + return MenuAction.setEnabled(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION, enabled) + | updateActionDescription(MenuAction.SELECT_AUDIO_LANGUAGE_ACTION); } private boolean updateDisplayModeAction() { TvViewUiManager uiManager = getMainActivity().getTvViewUiManager(); - boolean oldEnabled = MenuAction.SELECT_DISPLAY_MODE_ACTION.isEnabled(); - boolean newEnabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL) + boolean enabled = uiManager.isDisplayModeAvailable(DisplayMode.MODE_FULL) || uiManager.isDisplayModeAvailable(DisplayMode.MODE_ZOOM); - if (oldEnabled != newEnabled) { - MenuAction.SELECT_DISPLAY_MODE_ACTION.setEnabled(newEnabled); - return true; - } - return false; + // Use "|" operator for non-short-circuit evaluation. + return MenuAction.setEnabled(MenuAction.SELECT_DISPLAY_MODE_ACTION, enabled) + | updateActionDescription(MenuAction.SELECT_DISPLAY_MODE_ACTION); + } + + private boolean updateActionDescription(MenuAction action) { + return MenuAction.setActionDescription(action, + getMainActivity().getTvOptionsManager().getOptionString(action.getType())); } @Override @@ -183,9 +126,6 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { getMainActivity().getOverlayManager().getSideFragmentManager() .show(new DisplayModeFragment()); break; - case TvOptionsManager.OPTION_IN_APP_PIP: - getMainActivity().togglePipView(); - break; case TvOptionsManager.OPTION_SYSTEMWIDE_PIP: getMainActivity().enterPictureInPictureMode(); break; @@ -205,4 +145,4 @@ public class TvOptionsRowAdapter extends CustomizableOptionsRowAdapter { break; } } -} +}
\ No newline at end of file |