aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/menu
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/menu')
-rw-r--r--src/com/android/tv/menu/ActionCardView.java6
-rw-r--r--src/com/android/tv/menu/AppLinkCardView.java286
-rw-r--r--src/com/android/tv/menu/BaseCardView.java86
-rw-r--r--src/com/android/tv/menu/ChannelCardView.java140
-rw-r--r--src/com/android/tv/menu/ChannelsPosterPrefetcher.java9
-rw-r--r--src/com/android/tv/menu/ChannelsRow.java10
-rw-r--r--src/com/android/tv/menu/ChannelsRowAdapter.java198
-rw-r--r--src/com/android/tv/menu/ChannelsRowItem.java101
-rw-r--r--src/com/android/tv/menu/ItemListRowView.java20
-rw-r--r--src/com/android/tv/menu/Menu.java43
-rw-r--r--src/com/android/tv/menu/MenuAction.java69
-rw-r--r--src/com/android/tv/menu/MenuLayoutManager.java54
-rw-r--r--src/com/android/tv/menu/MenuRow.java5
-rw-r--r--src/com/android/tv/menu/MenuRowFactory.java33
-rw-r--r--src/com/android/tv/menu/MenuUpdater.java48
-rw-r--r--src/com/android/tv/menu/OptionsRowAdapter.java50
-rw-r--r--src/com/android/tv/menu/PartnerOptionsRowAdapter.java3
-rw-r--r--src/com/android/tv/menu/PipOptionsRowAdapter.java137
-rw-r--r--src/com/android/tv/menu/PlayControlsButton.java24
-rw-r--r--src/com/android/tv/menu/PlayControlsRowView.java194
-rw-r--r--src/com/android/tv/menu/PlaybackProgressBar.java168
-rw-r--r--src/com/android/tv/menu/SimpleCardView.java4
-rw-r--r--src/com/android/tv/menu/TvOptionsRowAdapter.java138
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