aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/ui')
-rw-r--r--src/com/android/tv/ui/AppLayerTvView.java14
-rw-r--r--src/com/android/tv/ui/BlockScreenView.java137
-rw-r--r--src/com/android/tv/ui/ChannelBannerView.java189
-rw-r--r--src/com/android/tv/ui/KeypadChannelSwitchView.java2
-rw-r--r--src/com/android/tv/ui/SelectInputView.java74
-rw-r--r--src/com/android/tv/ui/TunableTvView.java644
-rw-r--r--src/com/android/tv/ui/TvOverlayManager.java231
-rw-r--r--src/com/android/tv/ui/TvTransitionManager.java2
-rw-r--r--src/com/android/tv/ui/TvViewUiManager.java408
-rw-r--r--src/com/android/tv/ui/sidepanel/ActionItem.java20
-rw-r--r--src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java108
-rw-r--r--src/com/android/tv/ui/sidepanel/CompoundButtonItem.java21
-rw-r--r--src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java116
-rw-r--r--src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java45
-rw-r--r--src/com/android/tv/ui/sidepanel/Item.java12
-rw-r--r--src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java170
-rw-r--r--src/com/android/tv/ui/sidepanel/SettingsFragment.java137
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragment.java158
-rw-r--r--src/com/android/tv/ui/sidepanel/SideFragmentManager.java38
-rw-r--r--src/com/android/tv/ui/sidepanel/SimpleActionItem.java (renamed from src/com/android/tv/ui/sidepanel/SimpleItem.java)6
-rw-r--r--src/com/android/tv/ui/sidepanel/SubMenuItem.java13
-rw-r--r--src/com/android/tv/ui/sidepanel/SwitchItem.java5
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java13
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java42
-rw-r--r--src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java32
25 files changed, 1248 insertions, 1389 deletions
diff --git a/src/com/android/tv/ui/AppLayerTvView.java b/src/com/android/tv/ui/AppLayerTvView.java
index 09acb36b..625014ea 100644
--- a/src/com/android/tv/ui/AppLayerTvView.java
+++ b/src/com/android/tv/ui/AppLayerTvView.java
@@ -22,7 +22,8 @@ import android.util.AttributeSet;
import android.view.SurfaceView;
import android.view.View;
-import com.android.tv.experiments.Experiments;
+import com.android.tv.util.Debug;
+import com.android.tv.util.Utils;
/**
* A TvView class for application layer when multiple windows are being used in the app.
@@ -55,8 +56,17 @@ public class AppLayerTvView extends TvView {
public void onViewAdded(View child) {
if (child instanceof SurfaceView) {
// Note: See b/29118070 for detail.
- ((SurfaceView) child).setSecure(!Experiments.ENABLE_DEVELOPER_FEATURES.get());
+ ((SurfaceView) child).setSecure(!Utils.isDeveloper());
}
super.onViewAdded(child);
}
+
+ @Override
+ public void getLocationOnScreen(int[] outLocation) {
+ super.getLocationOnScreen(outLocation);
+
+ // The TvView.MySessionCallback.onSessionCreated() will call this method indirectly.
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
+ "AppLayerTvView.getLocationOnScreen, session created");
+ }
}
diff --git a/src/com/android/tv/ui/BlockScreenView.java b/src/com/android/tv/ui/BlockScreenView.java
index 52b9389d..09c167ca 100644
--- a/src/com/android/tv/ui/BlockScreenView.java
+++ b/src/com/android/tv/ui/BlockScreenView.java
@@ -21,32 +21,37 @@ import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
-import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.tv.R;
import com.android.tv.ui.TunableTvView.BlockScreenType;
-public class BlockScreenView extends LinearLayout {
+public class BlockScreenView extends FrameLayout {
private View mContainerView;
private View mImageContainer;
- private ImageView mNormalImageView;
- private ImageView mShrunkenImageView;
+ private ImageView mNormalLockIconView;
+ private ImageView mShrunkenLockIconView;
private View mSpace;
- private TextView mTextView;
+ private TextView mBlockingInfoTextView;
+ private ImageView mBackgroundImageView;
private final int mSpacingNormal;
private final int mSpacingShrunken;
- // Animators used for fade in/out of block screen icon.
- private Animator mFadeIn;
+ // Animator used to fade out the whole block screen.
private Animator mFadeOut;
+ // Animators used to fade in/out the block screen icon and info text.
+ private Animator mInfoFadeIn;
+ private Animator mInfoFadeOut;
+
public BlockScreenView(Context context) {
this(context, null, 0);
}
@@ -68,21 +73,32 @@ public class BlockScreenView extends LinearLayout {
super.onFinishInflate();
mContainerView = findViewById(R.id.block_screen_container);
mImageContainer = findViewById(R.id.image_container);
- mNormalImageView = (ImageView) findViewById(R.id.block_screen_icon);
- mShrunkenImageView = (ImageView) findViewById(R.id.block_screen_shrunken_icon);
+ mNormalLockIconView = (ImageView) findViewById(R.id.block_screen_icon);
+ mShrunkenLockIconView = (ImageView) findViewById(R.id.block_screen_shrunken_icon);
mSpace = findViewById(R.id.space);
- mTextView = (TextView) findViewById(R.id.block_screen_text);
- mFadeIn = AnimatorInflater.loadAnimator(getContext(),
- R.animator.tvview_block_screen_fade_in);
- mFadeIn.setTarget(mContainerView);
+ mBlockingInfoTextView = (TextView) findViewById(R.id.block_screen_text);
+ mBackgroundImageView = (ImageView) findViewById(R.id.background_image);
mFadeOut = AnimatorInflater.loadAnimator(getContext(),
R.animator.tvview_block_screen_fade_out);
- mFadeOut.setTarget(mContainerView);
+ mFadeOut.setTarget(this);
mFadeOut.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
+ setVisibility(GONE);
+ setBackgroundImage(null);
+ setAlpha(1.0f);
+ }
+ });
+ mInfoFadeIn = AnimatorInflater.loadAnimator(getContext(),
+ R.animator.tvview_block_screen_fade_in);
+ mInfoFadeIn.setTarget(mContainerView);
+ mInfoFadeOut = AnimatorInflater.loadAnimator(getContext(),
+ R.animator.tvview_block_screen_fade_out);
+ mInfoFadeOut.setTarget(mContainerView);
+ mInfoFadeOut.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
mContainerView.setVisibility(GONE);
- mContainerView.setAlpha(1f);
}
});
}
@@ -90,53 +106,54 @@ public class BlockScreenView extends LinearLayout {
/**
* Sets the normal image.
*/
- public void setImage(int resId) {
- mNormalImageView.setImageResource(resId);
+ public void setIconImage(int resId) {
+ mNormalLockIconView.setImageResource(resId);
updateSpaceVisibility();
}
/**
* Sets the scale type of the normal image.
*/
- public void setScaleType(ScaleType scaleType) {
- mNormalImageView.setScaleType(scaleType);
+ public void setIconScaleType(ScaleType scaleType) {
+ mNormalLockIconView.setScaleType(scaleType);
updateSpaceVisibility();
}
/**
- * Sets the shrunken image.
+ * Show or hide the image of this view.
*/
- public void setShrunkenImage(int resId) {
- mShrunkenImageView.setImageResource(resId);
+ public void setIconVisibility(boolean visible) {
+ mImageContainer.setVisibility(visible ? VISIBLE : GONE);
updateSpaceVisibility();
}
/**
- * Show or hide the image of this view.
+ * Sets the text message.
*/
- public void setImageVisibility(boolean visible) {
- mImageContainer.setVisibility(visible ? VISIBLE : GONE);
+ public void setInfoText(int resId) {
+ mBlockingInfoTextView.setText(resId);
updateSpaceVisibility();
}
/**
* Sets the text message.
*/
- public void setText(int resId) {
- mTextView.setText(resId);
+ public void setInfoText(String text) {
+ mBlockingInfoTextView.setText(text);
updateSpaceVisibility();
}
/**
- * Sets the text message.
+ * Sets the background image should be displayed in the block screen view. Passes {@code null}
+ * to remove the currently displayed background image.
*/
- public void setText(String text) {
- mTextView.setText(text);
- updateSpaceVisibility();
+ public void setBackgroundImage(Drawable backgroundImage) {
+ mBackgroundImageView.setVisibility(backgroundImage == null ? GONE : VISIBLE);
+ mBackgroundImageView.setImageDrawable(backgroundImage);
}
private void updateSpaceVisibility() {
- if (isImageViewVisible() && isTextViewVisible(mTextView)) {
+ if (isImageViewVisible() && isTextViewVisible(mBlockingInfoTextView)) {
mSpace.setVisibility(VISIBLE);
} else {
mSpace.setVisibility(GONE);
@@ -145,7 +162,8 @@ public class BlockScreenView extends LinearLayout {
private boolean isImageViewVisible() {
return mImageContainer.getVisibility() == VISIBLE
- && (isImageViewVisible(mNormalImageView) || isImageViewVisible(mShrunkenImageView));
+ && (isImageViewVisible(mNormalLockIconView)
+ || isImageViewVisible(mShrunkenLockIconView));
}
private static boolean isImageViewVisible(ImageView imageView) {
@@ -177,37 +195,39 @@ public class BlockScreenView extends LinearLayout {
mContainerView.setVisibility(GONE);
break;
case TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
- mNormalImageView.setVisibility(GONE);
- mShrunkenImageView.setVisibility(VISIBLE);
+ mNormalLockIconView.setVisibility(GONE);
+ mShrunkenLockIconView.setVisibility(VISIBLE);
mContainerView.setVisibility(VISIBLE);
+ mContainerView.setAlpha(1.0f);
break;
case TunableTvView.BLOCK_SCREEN_TYPE_NORMAL:
- mNormalImageView.setVisibility(VISIBLE);
- mShrunkenImageView.setVisibility(GONE);
+ mNormalLockIconView.setVisibility(VISIBLE);
+ mShrunkenLockIconView.setVisibility(GONE);
mContainerView.setVisibility(VISIBLE);
+ mContainerView.setAlpha(1.0f);
break;
}
} else {
switch (blockScreenType) {
case TunableTvView.BLOCK_SCREEN_TYPE_NO_UI:
if (mContainerView.getVisibility() == VISIBLE) {
- mFadeOut.start();
+ mInfoFadeOut.start();
}
break;
case TunableTvView.BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
- mNormalImageView.setVisibility(GONE);
- mShrunkenImageView.setVisibility(VISIBLE);
- mContainerView.setVisibility(VISIBLE);
+ mNormalLockIconView.setVisibility(GONE);
+ mShrunkenLockIconView.setVisibility(VISIBLE);
if (mContainerView.getVisibility() == GONE) {
- mFadeIn.start();
+ mContainerView.setVisibility(VISIBLE);
+ mInfoFadeIn.start();
}
break;
case TunableTvView.BLOCK_SCREEN_TYPE_NORMAL:
- mNormalImageView.setVisibility(VISIBLE);
- mShrunkenImageView.setVisibility(GONE);
- mContainerView.setVisibility(VISIBLE);
+ mNormalLockIconView.setVisibility(VISIBLE);
+ mShrunkenLockIconView.setVisibility(GONE);
if (mContainerView.getVisibility() == GONE) {
- mFadeIn.start();
+ mContainerView.setVisibility(VISIBLE);
+ mInfoFadeIn.start();
}
break;
}
@@ -216,26 +236,33 @@ public class BlockScreenView extends LinearLayout {
}
/**
- * Scales the contents view by the given {@code scale}.
+ * Adds a listener to the fade-in animation of info text and icons of the block screen.
*/
- public void scaleContainerView(float scale) {
- mContainerView.setScaleX(scale);
- mContainerView.setScaleY(scale);
+ public void addInfoFadeInAnimationListener(AnimatorListener listener) {
+ mInfoFadeIn.addListener(listener);
}
- public void addFadeOutAnimationListener(AnimatorListener listener) {
- mFadeOut.addListener(listener);
+ /**
+ * Fades out the block screen.
+ */
+ public void fadeOut() {
+ if (getVisibility() == VISIBLE && !mFadeOut.isStarted()) {
+ mFadeOut.start();
+ }
}
/**
* Ends the currently running animations.
*/
public void endAnimations() {
- if (mFadeIn != null && mFadeIn.isRunning()) {
- mFadeIn.end();
- }
if (mFadeOut != null && mFadeOut.isRunning()) {
mFadeOut.end();
}
+ if (mInfoFadeIn != null && mInfoFadeIn.isRunning()) {
+ mInfoFadeIn.end();
+ }
+ if (mInfoFadeOut != null && mInfoFadeOut.isRunning()) {
+ mInfoFadeOut.end();
+ }
}
}
diff --git a/src/com/android/tv/ui/ChannelBannerView.java b/src/com/android/tv/ui/ChannelBannerView.java
index 3cf4de83..a5d897f2 100644
--- a/src/com/android/tv/ui/ChannelBannerView.java
+++ b/src/com/android/tv/ui/ChannelBannerView.java
@@ -23,12 +23,9 @@ import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
-import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.media.tv.TvContentRating;
-import android.media.tv.TvContract;
import android.media.tv.TvInputInfo;
-import android.net.Uri;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.Spannable;
@@ -52,12 +49,13 @@ import android.widget.TextView;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
+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.data.StreamInfo;
import com.android.tv.dvr.DvrManager;
-import com.android.tv.dvr.ScheduledRecording;
+import com.android.tv.dvr.data.ScheduledRecording;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.util.ImageCache;
import com.android.tv.util.ImageLoader;
@@ -65,10 +63,6 @@ import com.android.tv.util.ImageLoader.ImageLoaderCallback;
import com.android.tv.util.ImageLoader.LoadTvInputLogoTask;
import com.android.tv.util.Utils;
-import junit.framework.Assert;
-
-import java.util.Objects;
-
/**
* A view to render channel banner.
*/
@@ -98,8 +92,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private static final String EMPTY_STRING = "";
- private static Program sNoProgram;
- private static Program sLockedChannelProgram;
+ private Program mNoProgram;
+ private Program mLockedChannelProgram;
private static String sClosedCaptionMark;
private final MainActivity mMainActivity;
@@ -123,6 +117,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private String mProgramDescriptionText;
private View mAnchorView;
private Channel mCurrentChannel;
+ private boolean mCurrentChannelLogoExists;
private Program mLastUpdatedProgram;
private final Handler mHandler = new Handler();
private final DvrManager mDvrManager;
@@ -130,6 +125,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private TvContentRating mBlockingContentRating;
private int mLockType;
+ private boolean mUpdateOnTune;
private Animator mResizeAnimator;
private int mCurrentHeight;
@@ -178,24 +174,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
};
- private final ContentObserver mProgramUpdateObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- // TODO: This {@code uri} argument may be a program which is not related to this
- // channel. Consider adding channel id as a parameter of program URI to avoid
- // unnecessary update.
- mHandler.post(mProgramUpdateRunnable);
- }
- };
-
- private final Runnable mProgramUpdateRunnable = new Runnable() {
- @Override
- public void run() {
- removeCallbacks(this);
- updateViews(null);
- }
- };
-
public ChannelBannerView(Context context) {
this(context, null);
}
@@ -243,39 +221,20 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mContentRatingsManager = TvApplication.getSingletons(getContext())
.getTvInputManagerHelper().getContentRatingsManager();
- if (sNoProgram == null) {
- sNoProgram = new Program.Builder()
- .setTitle(context.getString(R.string.channel_banner_no_title))
- .setDescription(EMPTY_STRING)
- .build();
- }
- if (sLockedChannelProgram == null){
- sLockedChannelProgram = new Program.Builder()
- .setTitle(context.getString(R.string.channel_banner_locked_channel_title))
- .setDescription(EMPTY_STRING)
- .build();
- }
+ mNoProgram = new Program.Builder()
+ .setTitle(context.getString(R.string.channel_banner_no_title))
+ .setDescription(EMPTY_STRING)
+ .build();
+ mLockedChannelProgram = new Program.Builder()
+ .setTitle(context.getString(R.string.channel_banner_locked_channel_title))
+ .setDescription(EMPTY_STRING)
+ .build();
if (sClosedCaptionMark == null) {
sClosedCaptionMark = context.getString(R.string.closed_caption);
}
}
@Override
- protected void onAttachedToWindow() {
- if (DEBUG) Log.d(TAG, "onAttachedToWindow");
- super.onAttachedToWindow();
- getContext().getContentResolver().registerContentObserver(TvContract.Programs.CONTENT_URI,
- true, mProgramUpdateObserver);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- if (DEBUG) Log.d(TAG, "onDetachedToWindow");
- getContext().getContentResolver().unregisterContentObserver(mProgramUpdateObserver);
- super.onDetachedFromWindow();
- }
-
- @Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -345,19 +304,17 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
* Set new lock type.
*
* @param lockType Any of LOCK_NONE, LOCK_PROGRAM_DETAIL, or LOCK_CHANNEL_INFO.
- * @return {@code true} only if lock type is changed
+ * @return the previous lock type of the channel banner.
* @throws IllegalArgumentException if lockType is invalid.
*/
- public boolean setLockType(int lockType) {
+ public int setLockType(int lockType) {
if (lockType != LOCK_NONE && lockType != LOCK_CHANNEL_INFO
&& lockType != LOCK_PROGRAM_DETAIL) {
throw new IllegalArgumentException("No such lock type " + lockType);
}
- if (mLockType != lockType) {
- mLockType = lockType;
- return true;
- }
- return false;
+ int previousLockType = mLockType;
+ mLockType = lockType;
+ return previousLockType;
}
/**
@@ -372,31 +329,34 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
/**
* Update channel banner view.
*
- * @param info A StreamInfo that includes stream information.
- * If it's {@code null}, only program information will be updated.
+ * @param updateOnTune {@false} denotes the channel banner is updated due to other reasons than
+ * tuning. The channel info will not be updated in this case.
*/
- public void updateViews(StreamInfo info) {
+ public void updateViews(boolean updateOnTune) {
resetAnimationEffects();
- Channel channel = mMainActivity.getCurrentChannel();
- if (!Objects.equals(mCurrentChannel, channel)) {
- mBlockingContentRating = null;
+ mChannelView.setVisibility(VISIBLE);
+ mUpdateOnTune = updateOnTune;
+ if (mUpdateOnTune) {
if (isShown()) {
scheduleHide();
}
- }
- mCurrentChannel = channel;
- mChannelView.setVisibility(VISIBLE);
- if (info != null) {
- // If the current channels between ChannelTuner and TvView are different,
- // the stream information should not be seen.
- updateStreamInfo(channel != null && channel.equals(info.getCurrentChannel()) ? info
- : null);
+ mBlockingContentRating = null;
+ mCurrentChannel = mMainActivity.getCurrentChannel();
+ mCurrentChannelLogoExists =
+ mCurrentChannel != null && mCurrentChannel.channelLogoExists();
+ updateStreamInfo(null);
updateChannelInfo();
}
updateProgramInfo(mMainActivity.getCurrentProgram());
+ mUpdateOnTune = false;
}
- private void updateStreamInfo(StreamInfo info) {
+ /**
+ * Update channel banner view with stream info.
+ *
+ * @param info A StreamInfo that includes stream information.
+ */
+ public void updateStreamInfo(StreamInfo info) {
// Update stream information in a channel.
if (mLockType != LOCK_CHANNEL_INFO && info != null) {
updateText(mClosedCaptionTextView, info.hasClosedCaption() ? sClosedCaptionMark
@@ -414,9 +374,6 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mAspectRatioTextView.setVisibility(View.GONE);
mResolutionTextView.setVisibility(View.GONE);
mAudioChannelTextView.setVisibility(View.GONE);
- for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
- mContentRatingsTextViews[i].setVisibility(View.GONE);
- }
}
}
@@ -467,7 +424,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
mChannelLogoImageView.setImageBitmap(null);
mChannelLogoImageView.setVisibility(View.GONE);
- if (mCurrentChannel != null) {
+ if (mCurrentChannel != null && mCurrentChannelLogoExists) {
mCurrentChannel.loadBitmap(getContext(), Channel.LOAD_IMAGE_TYPE_CHANNEL_LOGO,
mChannelLogoImageViewWidth, mChannelLogoImageViewHeight,
createChannelLogoCallback(this, mCurrentChannel));
@@ -550,8 +507,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (mResizeAnimator == null) {
String description = mProgramDescriptionTextView.getText().toString();
- boolean needFadeAnimation = !description.equals(mProgramDescriptionText);
- updateBannerHeight(needFadeAnimation);
+ boolean programDescriptionNeedFadeAnimation =
+ !description.equals(mProgramDescriptionText) && !mUpdateOnTune;
+ updateBannerHeight(programDescriptionNeedFadeAnimation);
} else {
mProgramInfoUpdatePendingByResizing = true;
}
@@ -559,9 +517,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
private void updateProgramInfo(Program program) {
if (mLockType == LOCK_CHANNEL_INFO) {
- program = sLockedChannelProgram;
+ program = mLockedChannelProgram;
} else if (program == null || !program.isValid() || TextUtils.isEmpty(program.getTitle())) {
- program = sNoProgram;
+ program = mNoProgram;
}
if (mLastUpdatedProgram == null
@@ -590,9 +548,9 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mProgramDescriptionText = program.getDescription();
}
String description = mProgramDescriptionTextView.getText().toString();
- boolean needFadeAnimation = isProgramChanged
- || !description.equals(mProgramDescriptionText);
- updateBannerHeight(needFadeAnimation);
+ boolean programDescriptionNeedFadeAnimation = (isProgramChanged
+ || !description.equals(mProgramDescriptionText)) && !mUpdateOnTune;
+ updateBannerHeight(programDescriptionNeedFadeAnimation);
} else {
mProgramInfoUpdatePendingByResizing = true;
}
@@ -603,7 +561,7 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
if (program == null) {
return;
}
- updateProgramTextView(program == sLockedChannelProgram, program.getTitle(),
+ updateProgramTextView(program == mLockedChannelProgram, program.getTitle(),
program.getEpisodeDisplayTitle(getContext()));
}
@@ -630,9 +588,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mProgramTextView.setText(text);
}
- int width = mProgramDescriptionTextViewWidth
- - ((mChannelLogoImageView.getVisibility() != View.VISIBLE)
- ? 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
+ int width = mProgramDescriptionTextViewWidth + (mCurrentChannelLogoExists ?
+ 0 : mChannelLogoImageViewWidth + mChannelLogoImageViewMarginStart);
ViewGroup.LayoutParams lp = mProgramTextView.getLayoutParams();
lp.width = width;
mProgramTextView.setLayoutParams(lp);
@@ -655,23 +612,32 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
}
private void updateProgramRatings(Program program) {
- if (mBlockingContentRating != null) {
- mContentRatingsTextViews[0].setText(
- mContentRatingsManager.getDisplayNameForRating(mBlockingContentRating));
- mContentRatingsTextViews[0].setVisibility(View.VISIBLE);
- for (int i = 1; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
+ if (mLockType == LOCK_CHANNEL_INFO) {
+ for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
mContentRatingsTextViews[i].setVisibility(View.GONE);
}
- return;
- }
- TvContentRating[] ratings = (program == null) ? null : program.getContentRatings();
- for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
- if (ratings == null || ratings.length <= i) {
- mContentRatingsTextViews[i].setVisibility(View.GONE);
+ } else if (mBlockingContentRating != null) {
+ String displayNameForRating =
+ mContentRatingsManager.getDisplayNameForRating(mBlockingContentRating);
+ if (!TextUtils.isEmpty(displayNameForRating)) {
+ mContentRatingsTextViews[0].setText(displayNameForRating);
+ mContentRatingsTextViews[0].setVisibility(View.VISIBLE);
} else {
- mContentRatingsTextViews[i].setText(
- mContentRatingsManager.getDisplayNameForRating(ratings[i]));
- mContentRatingsTextViews[i].setVisibility(View.VISIBLE);
+ mContentRatingsTextViews[0].setVisibility(View.GONE);
+ }
+ for (int i = 1; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
+ mContentRatingsTextViews[i].setVisibility(View.GONE);
+ }
+ } else {
+ TvContentRating[] ratings = (program == null) ? null : program.getContentRatings();
+ for (int i = 0; i < DISPLAYED_CONTENT_RATINGS_COUNT; i++) {
+ if (ratings == null || ratings.length <= i) {
+ mContentRatingsTextViews[i].setVisibility(View.GONE);
+ } else {
+ mContentRatingsTextViews[i].setText(
+ mContentRatingsManager.getDisplayNameForRating(ratings[i]));
+ mContentRatingsTextViews[i].setVisibility(View.VISIBLE);
+ }
}
}
}
@@ -769,8 +735,8 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
mLastUpdatedProgram = program;
}
- private void updateBannerHeight(boolean needFadeAnimation) {
- Assert.assertNull(mResizeAnimator);
+ private void updateBannerHeight(boolean needProgramDescriptionFadeAnimation) {
+ SoftPreconditions.checkState(mResizeAnimator == null);
// Need to measure the layout height with the new description text.
CharSequence oldDescription = mProgramDescriptionTextView.getText();
mProgramDescriptionTextView.setText(mProgramDescriptionText);
@@ -785,12 +751,13 @@ public class ChannelBannerView extends FrameLayout implements TvTransitionManage
layoutParams.height = targetHeight;
setLayoutParams(layoutParams);
}
- } else if (mCurrentHeight != targetHeight || needFadeAnimation) {
+ } else if (mCurrentHeight != targetHeight || needProgramDescriptionFadeAnimation) {
// Restore description text for fade in/out animation.
- if (needFadeAnimation) {
+ if (needProgramDescriptionFadeAnimation) {
mProgramDescriptionTextView.setText(oldDescription);
}
- mResizeAnimator = createResizeAnimator(targetHeight, needFadeAnimation);
+ mResizeAnimator =
+ createResizeAnimator(targetHeight, needProgramDescriptionFadeAnimation);
mResizeAnimator.start();
}
}
diff --git a/src/com/android/tv/ui/KeypadChannelSwitchView.java b/src/com/android/tv/ui/KeypadChannelSwitchView.java
index abc05bad..ac5d841d 100644
--- a/src/com/android/tv/ui/KeypadChannelSwitchView.java
+++ b/src/com/android/tv/ui/KeypadChannelSwitchView.java
@@ -39,7 +39,7 @@ import android.widget.TextView;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.analytics.DurationTimer;
+import com.android.tv.util.DurationTimer;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.SoftPreconditions;
import com.android.tv.data.Channel;
diff --git a/src/com/android/tv/ui/SelectInputView.java b/src/com/android/tv/ui/SelectInputView.java
index 5e25ae43..dc92111c 100644
--- a/src/com/android/tv/ui/SelectInputView.java
+++ b/src/com/android/tv/ui/SelectInputView.java
@@ -18,7 +18,6 @@ package com.android.tv.ui;
import android.content.Context;
import android.content.res.Resources;
-import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
@@ -37,14 +36,13 @@ import android.widget.TextView;
import com.android.tv.ApplicationSingletons;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.analytics.DurationTimer;
+import com.android.tv.util.DurationTimer;
import com.android.tv.analytics.Tracker;
import com.android.tv.data.Channel;
import com.android.tv.util.TvInputManagerHelper;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -58,7 +56,7 @@ public class SelectInputView extends VerticalGridView implements
private final TvInputManagerHelper mTvInputManagerHelper;
private final List<TvInputInfo> mInputList = new ArrayList<>();
- private final InputsComparator mComparator = new InputsComparator();
+ private final TvInputManagerHelper.HardwareInputComparator mComparator;
private final Tracker mTracker;
private final DurationTimer mViewDurationTimer = new DurationTimer();
private final TvInputCallback mTvInputCallback = new TvInputCallback() {
@@ -149,6 +147,8 @@ public class SelectInputView extends VerticalGridView implements
ApplicationSingletons appSingletons = TvApplication.getSingletons(context);
mTracker = appSingletons.getTracker();
mTvInputManagerHelper = appSingletons.getTvInputManagerHelper();
+ mComparator =
+ new TvInputManagerHelper.HardwareInputComparator(context, mTvInputManagerHelper);
Resources resources = context.getResources();
mInputItemHeight = resources.getDimensionPixelSize(R.dimen.input_banner_item_height);
@@ -385,72 +385,6 @@ public class SelectInputView extends VerticalGridView implements
}
}
- private class InputsComparator implements Comparator<TvInputInfo> {
- @Override
- public int compare(TvInputInfo lhs, TvInputInfo rhs) {
- if (lhs == null) {
- return (rhs == null) ? 0 : 1;
- }
- if (rhs == null) {
- return -1;
- }
-
- boolean enabledL = isInputEnabled(lhs);
- boolean enabledR = isInputEnabled(rhs);
- if (enabledL != enabledR) {
- return enabledL ? -1 : 1;
- }
-
- int priorityL = getPriority(lhs);
- int priorityR = getPriority(rhs);
- if (priorityL != priorityR) {
- return priorityR - priorityL;
- }
-
- String customLabelL = (String) lhs.loadCustomLabel(getContext());
- String customLabelR = (String) rhs.loadCustomLabel(getContext());
- if (!TextUtils.equals(customLabelL, customLabelR)) {
- customLabelL = customLabelL == null ? "" : customLabelL;
- customLabelR = customLabelR == null ? "" : customLabelR;
- return customLabelL.compareToIgnoreCase(customLabelR);
- }
-
- String labelL = (String) lhs.loadLabel(getContext());
- String labelR = (String) rhs.loadLabel(getContext());
- labelL = labelL == null ? "" : labelL;
- labelR = labelR == null ? "" : labelR;
- return labelL.compareToIgnoreCase(labelR);
- }
-
- private int getPriority(TvInputInfo info) {
- switch (info.getType()) {
- case TvInputInfo.TYPE_TUNER:
- return 9;
- case TvInputInfo.TYPE_HDMI:
- HdmiDeviceInfo hdmiInfo = info.getHdmiDeviceInfo();
- if (hdmiInfo != null && hdmiInfo.isCecDevice()) {
- return 8;
- }
- return 7;
- case TvInputInfo.TYPE_DVI:
- return 6;
- case TvInputInfo.TYPE_COMPONENT:
- return 5;
- case TvInputInfo.TYPE_SVIDEO:
- return 4;
- case TvInputInfo.TYPE_COMPOSITE:
- return 3;
- case TvInputInfo.TYPE_DISPLAY_PORT:
- return 2;
- case TvInputInfo.TYPE_VGA:
- return 1;
- case TvInputInfo.TYPE_SCART:
- default:
- return 0;
- }
- }
- }
-
/**
* A callback interface for the input selection.
*/
diff --git a/src/com/android/tv/ui/TunableTvView.java b/src/com/android/tv/ui/TunableTvView.java
index cbe459fb..48386698 100644
--- a/src/com/android/tv/ui/TunableTvView.java
+++ b/src/com/android/tv/ui/TunableTvView.java
@@ -19,9 +19,18 @@ package com.android.tv.ui;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
-import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.ApplicationErrorReport;
import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.media.PlaybackParams;
import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
@@ -33,12 +42,11 @@ import android.media.tv.TvView.TvInputCallback;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Bundle;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.UiThread;
-import android.support.v4.os.BuildCompat;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.AttributeSet;
@@ -47,23 +55,29 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceView;
import android.view.View;
-import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.android.tv.ApplicationSingletons;
+import com.android.tv.Features;
import com.android.tv.InputSessionManager;
import com.android.tv.InputSessionManager.TvViewSession;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.analytics.DurationTimer;
+import com.android.tv.data.Program;
+import com.android.tv.data.ProgramDataManager;
+import com.android.tv.parental.ParentalControlSettings;
+import com.android.tv.util.DurationTimer;
+import com.android.tv.util.Debug;
import com.android.tv.analytics.Tracker;
+import com.android.tv.common.BuildConfig;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.data.Channel;
import com.android.tv.data.StreamInfo;
import com.android.tv.data.WatchedHistoryManager;
import com.android.tv.parental.ContentRatingsManager;
import com.android.tv.recommendation.NotificationService;
+import com.android.tv.util.ImageLoader;
import com.android.tv.util.NetworkUtils;
import com.android.tv.util.PermissionUtils;
import com.android.tv.util.TvInputManagerHelper;
@@ -79,6 +93,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
public static final int VIDEO_UNAVAILABLE_REASON_NOT_TUNED = -1;
public static final int VIDEO_UNAVAILABLE_REASON_NO_RESOURCE = -2;
+ public static final int VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED = -3;
+ public static final int VIDEO_UNAVAILABLE_REASON_NONE = -100;
@Retention(RetentionPolicy.SOURCE)
@IntDef({BLOCK_SCREEN_TYPE_NO_UI, BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW, BLOCK_SCREEN_TYPE_NORMAL})
@@ -105,17 +121,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private static final int FADING_IN = 2;
private static final int FADING_OUT = 3;
- // It is too small to see the description text without PIP_BLOCK_SCREEN_SCALE_FACTOR.
- private static final float PIP_BLOCK_SCREEN_SCALE_FACTOR = 1.2f;
-
private AppLayerTvView mTvView;
private TvViewSession mTvViewSession;
private Channel mCurrentChannel;
private TvInputManagerHelper mInputManagerHelper;
private ContentRatingsManager mContentRatingsManager;
+ private ParentalControlSettings mParentalControlSettings;
+ private ProgramDataManager mProgramDataManager;
@Nullable
private WatchedHistoryManager mWatchedHistoryManager;
private boolean mStarted;
+ private String mTagetInputId;
private TvInputInfo mInputInfo;
private OnTuneListener mOnTuneListener;
private int mVideoWidth;
@@ -125,7 +141,6 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private float mVideoDisplayAspectRatio;
private int mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
private boolean mHasClosedCaption = false;
- private boolean mVideoAvailable;
private boolean mScreenBlocked;
private OnScreenBlockingChangedListener mOnScreenBlockedListener;
private TvContentRating mBlockedContentRating;
@@ -136,10 +151,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private boolean mParentControlEnabled;
private int mFixedSurfaceWidth;
private int mFixedSurfaceHeight;
- private boolean mIsPip;
- private int mScreenHeight;
- private int mShrunkenTvViewHeight;
private final boolean mCanModifyParentalControls;
+ private boolean mIsUnderShrunken;
@TimeShiftState private int mTimeShiftState = TIME_SHIFT_STATE_NONE;
private TimeShiftListener mTimeShiftListener;
@@ -150,21 +163,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
private final DurationTimer mChannelViewTimer = new DurationTimer();
private InternetCheckTask mInternetCheckTask;
- // A block screen view which has lock icon with black background.
- // This indicates that user's action is needed to play video.
+ // A block screen view to hide the real TV view underlying. It may be used to enforce parental
+ // control, or hide screen when there's no video available and show appropriate information.
private final BlockScreenView mBlockScreenView;
-
- // A View to hide screen when there's problem in video playback.
- private final BlockScreenView mHideScreenView;
-
- // A View to block screen until onContentAllowed is received if parental control is on.
- private final View mBlockScreenForTuneView;
+ private final int mTuningImageColorFilter;
// A spinner view to show buffering status.
private final View mBufferingSpinnerView;
- // A View for fade-in/out animation
private final View mDimScreenView;
+
private int mFadeState = FADED_IN;
private Runnable mActionAfterFade;
@@ -286,21 +294,77 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onVideoAvailable(String inputId) {
- unhideScreenByVideoAvailability();
+ if (DEBUG) Log.d(TAG, "onVideoAvailable: {inputId=" + inputId + "}");
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start up of Live TV ends," +
+ " TunableTvView.onVideoAvailable resets timer");
+ long startUpDurationTime = Debug.getTimer(Debug.TAG_START_UP_TIMER).reset();
+ Debug.removeTimer(Debug.TAG_START_UP_TIMER);
+ if (BuildConfig.ENG && startUpDurationTime > Debug.TIME_START_UP_DURATION_THRESHOLD) {
+ showAlertDialogForLongStartUp();
+ }
+ mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NONE;
+ updateBlockScreenAndMuting();
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
}
}
+ private void showAlertDialogForLongStartUp() {
+ new AlertDialog.Builder(getContext()).setTitle(
+ getContext().getString(R.string.settings_send_feedback))
+ .setMessage("Because the start up time of Live channels is too long," +
+ " please send feedback")
+ .setPositiveButton(android.R.string.ok,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ ApplicationErrorReport report = new ApplicationErrorReport();
+ report.packageName = report.processName = getContext()
+ .getApplicationContext().getPackageName();
+ report.time = System.currentTimeMillis();
+ report.type = ApplicationErrorReport.TYPE_CRASH;
+
+ // Add the crash info to add title of feedback automatically.
+ ApplicationErrorReport.CrashInfo crash = new
+ ApplicationErrorReport.CrashInfo();
+ crash.exceptionClassName =
+ "Live TV start up takes long time";
+ crash.exceptionMessage =
+ "The start up time of Live TV is too long";
+ report.crashInfo = crash;
+
+ intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
+ getContext().startActivity(intent);
+ }
+ })
+ .setNegativeButton(android.R.string.no, null)
+ .show();
+ }
+
@Override
public void onVideoUnavailable(String inputId, int reason) {
- hideScreenByVideoAvailability(inputId, reason);
+ if (reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING
+ && reason != TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
+ "TunableTvView.onVideoUnAvailable reason = (" + reason
+ + ") and removes timer");
+ Debug.removeTimer(Debug.TAG_START_UP_TIMER);
+ } else {
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
+ "TunableTvView.onVideoUnAvailable reason = (" + reason + ")");
+ }
+ mVideoUnavailableReason = reason;
+ if (closePipIfNeeded()) {
+ return;
+ }
+ updateBlockScreenAndMuting();
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(TunableTvView.this);
}
switch (reason) {
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
mTracker.sendChannelVideoUnavailable(mCurrentChannel, reason);
default:
@@ -310,8 +374,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onContentAllowed(String inputId) {
- mBlockScreenForTuneView.setVisibility(View.GONE);
- unblockScreenByContentRating();
+ mBlockedContentRating = null;
+ updateBlockScreenAndMuting();
if (mOnTuneListener != null) {
mOnTuneListener.onContentAllowed();
}
@@ -319,7 +383,14 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onContentBlocked(String inputId, TvContentRating rating) {
- blockScreenByContentRating(rating);
+ if (rating != null && rating.equals(mBlockedContentRating)) {
+ return;
+ }
+ mBlockedContentRating = rating;
+ if (closePipIfNeeded()) {
+ return;
+ }
+ updateBlockScreenAndMuting();
if (mOnTuneListener != null) {
mOnTuneListener.onContentBlocked();
}
@@ -327,6 +398,10 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public void onTimeShiftStatusChanged(String inputId, int status) {
+ if (DEBUG) {
+ Log.d(TAG, "onTimeShiftStatusChanged: {inputId=" + inputId + ", status=" + status +
+ "}");
+ }
boolean available = status == TvInputManager.TIME_SHIFT_STATUS_AVAILABLE;
setTimeShiftAvailable(available);
}
@@ -361,25 +436,17 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mTracker = appSingletons.getTracker();
mBlockScreenType = BLOCK_SCREEN_TYPE_NORMAL;
mBlockScreenView = (BlockScreenView) findViewById(R.id.block_screen);
- if (!mCanModifyParentalControls) {
- mBlockScreenView.setImage(R.drawable.ic_message_lock_no_permission);
- mBlockScreenView.setScaleType(ImageView.ScaleType.CENTER);
- } else {
- mBlockScreenView.setImage(R.drawable.ic_message_lock);
- }
- mBlockScreenView.setShrunkenImage(R.drawable.ic_message_lock_preview);
- mBlockScreenView.addFadeOutAnimationListener(new AnimatorListenerAdapter() {
+ mBlockScreenView.addInfoFadeInAnimationListener(new AnimatorListenerAdapter() {
@Override
- public void onAnimationEnd(Animator animation) {
+ public void onAnimationStart(Animator animation) {
adjustBlockScreenSpacingAndText();
}
});
- mHideScreenView = (BlockScreenView) findViewById(R.id.hide_screen);
- mHideScreenView.setImageVisibility(false);
mBufferingSpinnerView = findViewById(R.id.buffering_spinner);
- mBlockScreenForTuneView = findViewById(R.id.block_screen_for_tune);
- mDimScreenView = findViewById(R.id.dim);
+ mTuningImageColorFilter = getResources()
+ .getColor(R.color.tvview_block_image_color_filter, null);
+ mDimScreenView = findViewById(R.id.dim_screen);
mDimScreenView.animate().setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -397,27 +464,21 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
});
}
- public void initialize(AppLayerTvView tvView, boolean isPip, int screenHeight,
- int shrunkenTvViewHeight) {
- mTvView = tvView;
+ public void initialize(ProgramDataManager programDataManager,
+ TvInputManagerHelper tvInputManagerHelper) {
+ mTvView = (AppLayerTvView) findViewById(R.id.tv_view);
+ mProgramDataManager = programDataManager;
+ mInputManagerHelper = tvInputManagerHelper;
+ mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
+ mParentalControlSettings = tvInputManagerHelper.getParentalControlSettings();
if (mInputSessionManager != null) {
- mTvViewSession = mInputSessionManager.createTvViewSession(tvView, this, mCallback);
+ mTvViewSession = mInputSessionManager.createTvViewSession(mTvView, this, mCallback);
} else {
mTvView.setCallback(mCallback);
}
- mIsPip = isPip;
- mScreenHeight = screenHeight;
- mShrunkenTvViewHeight = shrunkenTvViewHeight;
- mTvView.setZOrderOnTop(isPip);
- copyLayoutParamsToTvView();
}
- public void start(TvInputManagerHelper tvInputManagerHelper) {
- mInputManagerHelper = tvInputManagerHelper;
- mContentRatingsManager = tvInputManagerHelper.getContentRatingsManager();
- if (mStarted) {
- return;
- }
+ public void start() {
mStarted = true;
}
@@ -431,7 +492,8 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
} else {
mTvView.tune(inputId, channelUri);
}
- hideScreenByVideoAvailability(inputId, TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
+ mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING;
+ updateBlockScreenAndMuting();
}
}
@@ -462,15 +524,16 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Reset TV view.
+ * Resets TV view.
*/
public void reset() {
resetInternal();
- hideScreenByVideoAvailability(null, VIDEO_UNAVAILABLE_REASON_NOT_TUNED);
+ mVideoUnavailableReason = VIDEO_UNAVAILABLE_REASON_NOT_TUNED;
+ updateBlockScreenAndMuting();
}
/**
- * Reset TV view to acquire the recording session.
+ * Resets TV view to acquire the recording session.
*/
public void resetByRecording() {
resetInternal();
@@ -497,6 +560,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mWatchedHistoryManager = watchedHistoryManager;
}
+ /**
+ * Sets if the TunableTvView is under shrunken.
+ */
+ public void setIsUnderShrunken(boolean isUnderShrunken) {
+ mIsUnderShrunken = isUnderShrunken;
+ }
+
public boolean isPlaying() {
return mStarted;
}
@@ -506,8 +576,9 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
*/
public void onParentalControlChanged(boolean enabled) {
mParentControlEnabled = enabled;
- if (!mParentControlEnabled) {
- mBlockScreenForTuneView.setVisibility(View.GONE);
+ if (!enabled) {
+ // Unblock screen immediately if parental control is turned off
+ updateBlockScreenAndMuting();
}
}
@@ -519,6 +590,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
* if the state is disconnected or channelId doesn't exist, it returns false.
*/
public boolean tuneTo(Channel channel, Bundle params, OnTuneListener listener) {
+ Debug.getTimer(Debug.TAG_START_UP_TIMER).log("TunableTvView.tuneTo");
if (!mStarted) {
throw new IllegalStateException("TvView isn't started");
}
@@ -541,6 +613,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
&& params.getString(NotificationService.TUNE_PARAMS_RECOMMENDATION_TYPE) != null;
boolean needSurfaceSizeUpdate = false;
if (!inputInfo.equals(mInputInfo)) {
+ mTagetInputId = inputInfo.getId();
mInputInfo = inputInfo;
mCanReceiveInputEvent = getContext().getPackageManager().checkPermission(
PERMISSION_RECEIVE_INPUT_EVENT, mInputInfo.getServiceInfo().packageName)
@@ -560,6 +633,7 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
mVideoDisplayAspectRatio = 0f;
mAudioChannelCount = StreamInfo.AUDIO_CHANNEL_COUNT_UNKNOWN;
mHasClosedCaption = false;
+ mBlockedContentRating = null;
mTimeShiftCurrentPositionMs = TvInputManager.TIME_SHIFT_INVALID_TIME;
// To reduce the IPCs, unregister the callback here and register it when necessary.
mTvView.setTimeShiftPositionCallback(null);
@@ -569,19 +643,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
// So we need to call SurfaceHolder.setFixedSize for the new SurfaceView.
getSurfaceView().getHolder().setFixedSize(mFixedSurfaceWidth, mFixedSurfaceHeight);
}
- hideScreenByVideoAvailability(mInputInfo.getId(),
- TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING);
+ mVideoUnavailableReason = TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING;
if (mTvViewSession != null) {
mTvViewSession.tune(channel, params, listener);
} else {
mTvView.tune(mInputInfo.getId(), mCurrentChannel.getUri(), params);
}
- unblockScreenByContentRating();
- if (channel.isPassthrough()) {
- mBlockScreenForTuneView.setVisibility(View.GONE);
- } else if (mParentControlEnabled) {
- mBlockScreenForTuneView.setVisibility(View.VISIBLE);
- }
+ updateBlockScreenAndMuting();
if (mOnTuneListener != null) {
mOnTuneListener.onStreamInfoChanged(this);
}
@@ -711,7 +779,13 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
public boolean isVideoAvailable() {
- return mVideoAvailable;
+ return mVideoUnavailableReason == VIDEO_UNAVAILABLE_REASON_NONE;
+ }
+
+ @Override
+ public boolean isVideoOrAudioAvailable() {
+ return mVideoUnavailableReason == VIDEO_UNAVAILABLE_REASON_NONE
+ || mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY;
}
@Override
@@ -747,12 +821,50 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Returns if the screen is blocked by {@link #blockScreen()}.
+ * Gets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying
+ * {@link TvView}, which is the actual view to play live TV videos.
+ */
+ public MarginLayoutParams getTvViewLayoutParams() {
+ return (MarginLayoutParams) mTvView.getLayoutParams();
+ }
+
+ /**
+ * Sets {@link android.view.ViewGroup.MarginLayoutParams} of the underlying
+ * {@link TvView}, which is the actual view to play live TV videos.
+ */
+ public void setTvViewLayoutParams(MarginLayoutParams layoutParams) {
+ mTvView.setLayoutParams(layoutParams);
+ }
+
+ /**
+ * Gets the underlying {@link AppLayerTvView}, which is the actual view to play live TV videos.
+ */
+ public TvView getTvView() {
+ return mTvView;
+ }
+
+ /**
+ * Returns if the screen is blocked, either by {@link #blockOrUnblockScreen(boolean)} or because
+ * the content is blocked.
+ */
+ public boolean isBlocked() {
+ return isScreenBlocked() || isContentBlocked();
+ }
+
+ /**
+ * Returns if the screen is blocked by {@link #blockOrUnblockScreen(boolean)}.
*/
public boolean isScreenBlocked() {
return mScreenBlocked;
}
+ /**
+ * Returns {@code true} if the content is blocked, otherwise {@code false}.
+ */
+ public boolean isContentBlocked() {
+ return mBlockedContentRating != null;
+ }
+
public void setOnScreenBlockedListener(OnScreenBlockingChangedListener listener) {
mOnScreenBlockedListener = listener;
}
@@ -766,77 +878,23 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
}
/**
- * Locks current TV screen and mutes.
+ * Blocks/unblocks current TV screen and mutes.
* There would be black screen with lock icon in order to show that
* screen block is intended and not an error.
- * TODO: Accept parameter to show lock icon or not.
+ *
+ * @param blockOrUnblock {@code true} to block the screen, or {@code false} to unblock.
*/
- public void blockScreen() {
- mScreenBlocked = true;
- checkBlockScreenAndMuteNeeded();
- if (mOnScreenBlockedListener != null) {
- mOnScreenBlockedListener.onScreenBlockingChanged(true);
- }
- }
-
- private void blockScreenByContentRating(TvContentRating rating) {
- mBlockedContentRating = rating;
- checkBlockScreenAndMuteNeeded();
- }
-
- @Override
- @SuppressLint("RtlHardcoded")
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mIsPip) {
- int height = bottom - top;
- float scale;
- if (mBlockScreenType == BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW) {
- scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mShrunkenTvViewHeight;
- } else {
- scale = height * PIP_BLOCK_SCREEN_SCALE_FACTOR / mScreenHeight;
- }
- // TODO: need to get UX confirmation.
- mBlockScreenView.scaleContainerView(scale);
+ public void blockOrUnblockScreen(boolean blockOrUnblock) {
+ if (mScreenBlocked == blockOrUnblock) {
+ return;
}
- }
-
- @Override
- public void setLayoutParams(ViewGroup.LayoutParams params) {
- super.setLayoutParams(params);
- if (mTvView != null) {
- copyLayoutParamsToTvView();
+ mScreenBlocked = blockOrUnblock;
+ if (closePipIfNeeded()) {
+ return;
}
- }
-
- private void copyLayoutParamsToTvView() {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
- FrameLayout.LayoutParams tvViewLp = (FrameLayout.LayoutParams) mTvView.getLayoutParams();
- if (tvViewLp.bottomMargin != lp.bottomMargin
- || tvViewLp.topMargin != lp.topMargin
- || tvViewLp.leftMargin != lp.leftMargin
- || tvViewLp.rightMargin != lp.rightMargin
- || tvViewLp.gravity != lp.gravity
- || tvViewLp.height != lp.height
- || tvViewLp.width != lp.width) {
- if (lp.topMargin == tvViewLp.topMargin && lp.leftMargin == tvViewLp.leftMargin
- && !BuildCompat.isAtLeastN()) {
- // HACK: If top and left position aren't changed and SurfaceHolder.setFixedSize is
- // used, SurfaceView doesn't catch the width and height change. It causes a bug that
- // PIP size change isn't shown when PIP is located TOP|LEFT. So we adjust 1 px for
- // small size PIP as a workaround.
- // Note: This framework issue has been fixed from NYC.
- tvViewLp.leftMargin = lp.leftMargin + 1;
- } else {
- tvViewLp.leftMargin = lp.leftMargin;
- }
- tvViewLp.topMargin = lp.topMargin;
- tvViewLp.bottomMargin = lp.bottomMargin;
- tvViewLp.rightMargin = lp.rightMargin;
- tvViewLp.gravity = lp.gravity;
- tvViewLp.height = lp.height;
- tvViewLp.width = lp.width;
- mTvView.setLayoutParams(tvViewLp);
+ updateBlockScreenAndMuting();
+ if (mOnScreenBlockedListener != null) {
+ mOnScreenBlockedListener.onScreenBlockingChanged(blockOrUnblock);
}
}
@@ -859,35 +917,67 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
* @param type The type of block screen to set.
*/
public void setBlockScreenType(@BlockScreenType int type) {
- // TODO: need to support the transition from NORMAL to SHRUNKEN and vice verse.
if (mBlockScreenType != type) {
mBlockScreenType = type;
- updateBlockScreenUI(true);
+ updateBlockScreen(true);
}
}
- private void updateBlockScreenUI(boolean animation) {
+ private void updateBlockScreen(boolean animation) {
mBlockScreenView.endAnimations();
-
- if (!mScreenBlocked && mBlockedContentRating == null) {
- mBlockScreenView.setVisibility(GONE);
- return;
- }
-
- mBlockScreenView.setVisibility(VISIBLE);
- if (!animation || mBlockScreenType != TunableTvView.BLOCK_SCREEN_TYPE_NO_UI) {
- adjustBlockScreenSpacingAndText();
+ int blockReason = (mScreenBlocked || mBlockedContentRating != null)
+ && mParentControlEnabled ? VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED
+ : mVideoUnavailableReason;
+ if (blockReason != VIDEO_UNAVAILABLE_REASON_NONE) {
+ mBufferingSpinnerView.setVisibility(
+ blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING
+ || blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING ?
+ VISIBLE : GONE);
+ if (!animation) {
+ adjustBlockScreenSpacingAndText();
+ }
+ if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING) {
+ return;
+ }
+ mBlockScreenView.setVisibility(VISIBLE);
+ mBlockScreenView.setBackgroundImage(null);
+ if (blockReason == VIDEO_UNAVAILABLE_REASON_SCREEN_BLOCKED) {
+ mBlockScreenView.setIconVisibility(true);
+ if (!mCanModifyParentalControls) {
+ mBlockScreenView.setIconImage(R.drawable.ic_message_lock_no_permission);
+ mBlockScreenView.setIconScaleType(ImageView.ScaleType.CENTER);
+ } else {
+ mBlockScreenView.setIconImage(R.drawable.ic_message_lock);
+ mBlockScreenView.setIconScaleType(ImageView.ScaleType.FIT_CENTER);
+ }
+ } else {
+ if (mInternetCheckTask != null) {
+ mInternetCheckTask.cancel(true);
+ mInternetCheckTask = null;
+ }
+ mBlockScreenView.setIconVisibility(false);
+ if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING) {
+ showImageForTuningIfNeeded();
+ } else if (blockReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN
+ && mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) {
+ mInternetCheckTask = new InternetCheckTask();
+ mInternetCheckTask.execute();
+ }
+ }
+ mBlockScreenView.onBlockStatusChanged(mBlockScreenType, animation);
+ } else {
+ mBufferingSpinnerView.setVisibility(GONE);
+ if (mBlockScreenView.getVisibility() == VISIBLE) {
+ mBlockScreenView.fadeOut();
+ }
}
- mBlockScreenView.onBlockStatusChanged(mBlockScreenType, animation);
}
private void adjustBlockScreenSpacingAndText() {
- // TODO: need to add animation for padding change when the block screen type is changed
- // NORMAL to SHRUNKEN and vice verse.
mBlockScreenView.setSpacing(mBlockScreenType);
String text = getBlockScreenText();
if (text != null) {
- mBlockScreenView.setText(text);
+ mBlockScreenView.setInfoText(text);
}
}
@@ -896,151 +986,121 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
* Note that returning {@code null} value means that the current text should not be changed.
*/
private String getBlockScreenText() {
- if (mScreenBlocked) {
+ // TODO: add a test for this method
+ Resources res = getResources();
+ if (mScreenBlocked && mParentControlEnabled) {
switch (mBlockScreenType) {
case BLOCK_SCREEN_TYPE_NO_UI:
case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
return "";
case BLOCK_SCREEN_TYPE_NORMAL:
if (mCanModifyParentalControls) {
- return getResources().getString(R.string.tvview_channel_locked);
+ return res.getString(R.string.tvview_channel_locked);
} else {
- return getResources().getString(
- R.string.tvview_channel_locked_no_permission);
+ return res.getString(R.string.tvview_channel_locked_no_permission);
}
}
- } else if (mBlockedContentRating != null) {
+ } else if (mBlockedContentRating != null && mParentControlEnabled) {
String name = mContentRatingsManager.getDisplayNameForRating(mBlockedContentRating);
switch (mBlockScreenType) {
case BLOCK_SCREEN_TYPE_NO_UI:
return "";
case BLOCK_SCREEN_TYPE_SHRUNKEN_TV_VIEW:
if (TextUtils.isEmpty(name)) {
- return getResources().getString(R.string.shrunken_tvview_content_locked);
+ return res.getString(R.string.shrunken_tvview_content_locked);
+ } else if (name.equals(res.getString(R.string.unrated_rating_name))) {
+ return res.getString(R.string.shrunken_tvview_content_locked_unrated);
} else {
- return getContext().getString(
- R.string.shrunken_tvview_content_locked_format, name);
+ return res.getString(R.string.shrunken_tvview_content_locked_format, name);
}
case BLOCK_SCREEN_TYPE_NORMAL:
if (TextUtils.isEmpty(name)) {
if (mCanModifyParentalControls) {
- return getResources().getString(R.string.tvview_content_locked);
+ return res.getString(R.string.tvview_content_locked);
} else {
- return getResources().getString(
- R.string.tvview_content_locked_no_permission);
+ return res.getString(R.string.tvview_content_locked_no_permission);
}
} else {
if (mCanModifyParentalControls) {
- return getContext().getString(
- R.string.tvview_content_locked_format, name);
+ return name.equals(res.getString(R.string.unrated_rating_name))
+ ? res.getString(R.string.tvview_content_locked_unrated)
+ : res.getString(R.string.tvview_content_locked_format, name);
} else {
- return getContext().getString(
- R.string.tvview_content_locked_format_no_permission, name);
+ return name.equals(res.getString(R.string.unrated_rating_name))
+ ? res.getString(
+ R.string.tvview_content_locked_unrated_no_permission)
+ : res.getString(
+ R.string.tvview_content_locked_format_no_permission,
+ name);
}
}
}
+ } else if (mVideoUnavailableReason != VIDEO_UNAVAILABLE_REASON_NONE) {
+ switch (mVideoUnavailableReason) {
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
+ return res.getString(R.string.tvview_msg_audio_only);
+ case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
+ return res.getString(R.string.tvview_msg_weak_signal);
+ case VIDEO_UNAVAILABLE_REASON_NO_RESOURCE:
+ return getTuneConflictMessage();
+ default:
+ return "";
+ }
}
return null;
}
- private void checkBlockScreenAndMuteNeeded() {
- updateBlockScreenUI(false);
- if (mScreenBlocked || mBlockedContentRating != null) {
- mute();
- if (mIsPip) {
- // If we don't make mTvView invisible, some frames are leaked when a user changes
- // PIP layout in options.
- // Note: When video is unavailable, we keep the mTvView's visibility, because
- // TIS implementation may not send video available with no surface.
- mTvView.setVisibility(View.INVISIBLE);
- }
- } else {
- unmuteIfPossible();
- if (mIsPip) {
- mTvView.setVisibility(View.VISIBLE);
- }
+ private boolean closePipIfNeeded() {
+ if (Features.PICTURE_IN_PICTURE.isEnabled(getContext())
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ && ((Activity) getContext()).isInPictureInPictureMode()
+ && (mScreenBlocked
+ || mBlockedContentRating != null
+ || mVideoUnavailableReason
+ == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN)) {
+ ((Activity) getContext()).finish();
+ return true;
}
+ return false;
}
- public void unblockScreen() {
- mScreenBlocked = false;
- checkBlockScreenAndMuteNeeded();
- if (mOnScreenBlockedListener != null) {
- mOnScreenBlockedListener.onScreenBlockingChanged(false);
- }
+ private void updateBlockScreenAndMuting() {
+ updateBlockScreen(false);
+ updateMuteStatus();
}
- private void unblockScreenByContentRating() {
- mBlockedContentRating = null;
- checkBlockScreenAndMuteNeeded();
+ private boolean shouldShowImageForTuning() {
+ if (mVideoUnavailableReason != TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING
+ || mScreenBlocked || mBlockedContentRating != null || mCurrentChannel == null
+ || mIsUnderShrunken || getWidth() == 0 || getWidth() == 0 || !isBundledInput()) {
+ return false;
+ }
+ Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId());
+ if (currentProgram == null) {
+ return false;
+ }
+ TvContentRating rating =
+ mParentalControlSettings.getBlockedRating(currentProgram.getContentRatings());
+ return !(mParentControlEnabled && rating != null);
}
- @UiThread
- private void hideScreenByVideoAvailability(String inputId, int reason) {
- mVideoAvailable = false;
- mVideoUnavailableReason = reason;
- if (mInternetCheckTask != null) {
- mInternetCheckTask.cancel(true);
- mInternetCheckTask = null;
- }
- switch (reason) {
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY:
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setImageVisibility(false);
- mHideScreenView.setText(R.string.tvview_msg_audio_only);
- mBufferingSpinnerView.setVisibility(GONE);
- unmuteIfPossible();
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_BUFFERING:
- mBufferingSpinnerView.setVisibility(VISIBLE);
- mute();
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL:
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setText(R.string.tvview_msg_weak_signal);
- mBufferingSpinnerView.setVisibility(GONE);
- mute();
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_TUNING:
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setImageVisibility(false);
- mHideScreenView.setText(null);
- mBufferingSpinnerView.setVisibility(VISIBLE);
- mute();
- break;
- case VIDEO_UNAVAILABLE_REASON_NOT_TUNED:
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setImageVisibility(false);
- mHideScreenView.setText(null);
- mBufferingSpinnerView.setVisibility(GONE);
- mute();
- break;
- case VIDEO_UNAVAILABLE_REASON_NO_RESOURCE:
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setImageVisibility(false);
- mHideScreenView.setText(getTuneConflictMessage(inputId));
- mBufferingSpinnerView.setVisibility(GONE);
- mute();
- break;
- case TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN:
- default:
- mHideScreenView.setVisibility(VISIBLE);
- mHideScreenView.setImageVisibility(false);
- mHideScreenView.setText(null);
- mBufferingSpinnerView.setVisibility(GONE);
- mute();
- if (mCurrentChannel != null && !mCurrentChannel.isPhysicalTunerChannel()) {
- mInternetCheckTask = new InternetCheckTask();
- mInternetCheckTask.execute();
- }
- break;
+ private void showImageForTuningIfNeeded() {
+ if (shouldShowImageForTuning()) {
+ if (mCurrentChannel == null) {
+ return;
+ }
+ Program currentProgram = mProgramDataManager.getCurrentProgram(mCurrentChannel.getId());
+ if (currentProgram != null) {
+ currentProgram.loadPosterArt(getContext(), getWidth(), getHeight(),
+ createProgramPosterArtCallback(mCurrentChannel.getId()));
+ }
}
}
- private String getTuneConflictMessage(String inputId) {
- if (inputId != null) {
- TvInputInfo input = mInputManager.getTvInputInfo(inputId);
- Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(inputId);
+ private String getTuneConflictMessage() {
+ if (mTagetInputId != null) {
+ TvInputInfo input = mInputManager.getTvInputInfo(mTagetInputId);
+ Long timeMs = mInputSessionManager.getEarliestRecordingSessionEndTimeMs(mTagetInputId);
if (timeMs != null) {
return getResources().getQuantityString(R.plurals.tvview_msg_input_no_resource,
input.getTunerCount(),
@@ -1050,27 +1110,36 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return null;
}
- private void unhideScreenByVideoAvailability() {
- mVideoAvailable = true;
- mHideScreenView.setVisibility(GONE);
- mBufferingSpinnerView.setVisibility(GONE);
- unmuteIfPossible();
- }
-
- private void unmuteIfPossible() {
- if (mVideoAvailable && !mScreenBlocked && mBlockedContentRating == null) {
- unmute();
+ private void updateMuteStatus() {
+ // Workaround: TunerTvInputService uses AC3 pass-through implementation, which disables
+ // audio tracks to enforce the mute request. We don't want to send mute request if we are
+ // not going to block the screen to prevent the video jankiness resulted by disabling audio
+ // track before the playback is started. In other way, we should send unmute request before
+ // the playback is started, because TunerTvInput will remember the muted state and mute
+ // itself right way when the playback is going to be started, which results the initial
+ // jankiness, too.
+ boolean isBundledInput = isBundledInput();
+ if ((isBundledInput || isVideoOrAudioAvailable()) && !mScreenBlocked
+ && mBlockedContentRating == null) {
+ if (mIsMuted) {
+ mIsMuted = false;
+ mTvView.setStreamVolume(mVolume);
+ }
+ } else {
+ if (!mIsMuted) {
+ if ((mInputInfo == null || isBundledInput)
+ && !mScreenBlocked && mBlockedContentRating == null) {
+ return;
+ }
+ mIsMuted = true;
+ mTvView.setStreamVolume(0);
+ }
}
}
- private void mute() {
- mIsMuted = true;
- mTvView.setStreamVolume(0);
- }
-
- private void unmute() {
- mIsMuted = false;
- mTvView.setStreamVolume(mVolume);
+ private boolean isBundledInput() {
+ return mInputInfo != null && mInputInfo.getType() == TvInputInfo.TYPE_TUNER
+ && Utils.isBundledInput(mInputInfo.getId());
}
/** Returns true if this view is faded out. */
@@ -1268,6 +1337,24 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
return mTimeShiftCurrentPositionMs;
}
+ private ImageLoader.ImageLoaderCallback<BlockScreenView> createProgramPosterArtCallback(
+ final long channelId) {
+ return new ImageLoader.ImageLoaderCallback<BlockScreenView>(mBlockScreenView) {
+ @Override
+ public void onBitmapLoaded(BlockScreenView view, @Nullable Bitmap posterArt) {
+ if (posterArt == null || getCurrentChannel() == null
+ || channelId != getCurrentChannel().getId()
+ || !shouldShowImageForTuning()) {
+ return;
+ }
+ Drawable drawablePosterArt = new BitmapDrawable(view.getResources(), posterArt);
+ drawablePosterArt.mutate().setColorFilter(
+ mTuningImageColorFilter, PorterDuff.Mode.SRC_OVER);
+ view.setBackgroundImage(drawablePosterArt);
+ }
+ };
+ }
+
/**
* Used to receive the time-shift events.
*/
@@ -1304,11 +1391,12 @@ public class TunableTvView extends FrameLayout implements StreamInfo {
@Override
protected void onPostExecute(Boolean networkAvailable) {
mInternetCheckTask = null;
- if (!mVideoAvailable && !networkAvailable && isAttachedToWindow()
+ if (!networkAvailable && isAttachedToWindow()
+ && !mScreenBlocked && mBlockedContentRating == null
&& mVideoUnavailableReason == TvInputManager.VIDEO_UNAVAILABLE_REASON_UNKNOWN) {
- mHideScreenView.setImageVisibility(true);
- mHideScreenView.setImage(R.drawable.ic_sad_cloud);
- mHideScreenView.setText(R.string.tvview_msg_no_internet_connection);
+ mBlockScreenView.setIconVisibility(true);
+ mBlockScreenView.setIconImage(R.drawable.ic_sad_cloud);
+ mBlockScreenView.setInfoText(R.string.tvview_msg_no_internet_connection);
}
}
}
diff --git a/src/com/android/tv/ui/TvOverlayManager.java b/src/com/android/tv/ui/TvOverlayManager.java
index e14b286b..9324742e 100644
--- a/src/com/android/tv/ui/TvOverlayManager.java
+++ b/src/com/android/tv/ui/TvOverlayManager.java
@@ -20,6 +20,7 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentManager.OnBackStackChangedListener;
import android.content.Intent;
+import android.media.tv.TvContentRating;
import android.media.tv.TvInputInfo;
import android.os.Bundle;
import android.os.Handler;
@@ -39,6 +40,7 @@ import com.android.tv.MainActivity.KeyHandlerResultType;
import com.android.tv.R;
import com.android.tv.TimeShiftManager;
import com.android.tv.TvApplication;
+import com.android.tv.TvOptionsManager;
import com.android.tv.analytics.Tracker;
import com.android.tv.common.WeakHandler;
import com.android.tv.common.feature.CommonFeatures;
@@ -46,14 +48,16 @@ import com.android.tv.common.ui.setup.OnActionClickListener;
import com.android.tv.common.ui.setup.SetupFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.data.ChannelDataManager;
+import com.android.tv.dialog.DvrHistoryDialogFragment;
import com.android.tv.dialog.FullscreenDialogFragment;
+import com.android.tv.dialog.HalfSizedDialogFragment;
import com.android.tv.dialog.PinDialogFragment;
import com.android.tv.dialog.RecentlyWatchedDialogFragment;
import com.android.tv.dialog.SafeDismissDialogFragment;
import com.android.tv.dvr.DvrDataManager;
-import com.android.tv.dvr.ui.DvrActivity;
-import com.android.tv.dvr.ui.HalfSizedDialogFragment;
+import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.guide.ProgramGuide;
+import com.android.tv.license.LicenseDialogFragment;
import com.android.tv.menu.Menu;
import com.android.tv.menu.Menu.MenuShowReason;
import com.android.tv.menu.MenuRowFactory;
@@ -62,7 +66,6 @@ import com.android.tv.onboarding.NewSourcesFragment;
import com.android.tv.onboarding.SetupSourcesFragment;
import com.android.tv.search.ProgramGuideSearchFragment;
import com.android.tv.ui.TvTransitionManager.SceneType;
-import com.android.tv.ui.sidepanel.SettingsFragment;
import com.android.tv.ui.sidepanel.SideFragmentManager;
import com.android.tv.ui.sidepanel.parentalcontrols.RatingsFragment;
import com.android.tv.util.TvInputManagerHelper;
@@ -157,15 +160,47 @@ public class TvOverlayManager {
// Used for the padded print of the overlay type.
private static final int NUM_OVERLAY_TYPES = 9;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW, UPDATE_CHANNEL_BANNER_REASON_TUNE,
+ UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST, UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO,
+ UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK,
+ UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO})
+ private @interface ChannelBannerUpdateReason {}
+ /**
+ * Updates channel banner because the channel banner is forced to show.
+ */
+ public static final int UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW = 1;
+ /**
+ * Updates channel banner because of tuning.
+ */
+ public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE = 2;
+ /**
+ * Updates channel banner because of fast tuning.
+ */
+ public static final int UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST = 3;
+ /**
+ * Updates channel banner because of info updating.
+ */
+ public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_INFO = 4;
+ /**
+ * Updates channel banner because the current watched channel is locked or unlocked.
+ */
+ public static final int UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK = 5;
+ /**
+ * Updates channel banner because of stream info updating.
+ */
+ public static final int UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO = 6;
+
private static final String FRAGMENT_TAG_SETUP_SOURCES = "tag_setup_sources";
private static final String FRAGMENT_TAG_NEW_SOURCES = "tag_new_sources";
private static final Set<String> AVAILABLE_DIALOG_TAGS = new HashSet<>();
static {
AVAILABLE_DIALOG_TAGS.add(RecentlyWatchedDialogFragment.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(DvrHistoryDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(PinDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(FullscreenDialogFragment.DIALOG_TAG);
- AVAILABLE_DIALOG_TAGS.add(SettingsFragment.LicenseActionItem.DIALOG_TAG);
+ AVAILABLE_DIALOG_TAGS.add(LicenseDialogFragment.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(RatingsFragment.AttributionItem.DIALOG_TAG);
AVAILABLE_DIALOG_TAGS.add(HalfSizedDialogFragment.DIALOG_TAG);
}
@@ -176,8 +211,10 @@ public class TvOverlayManager {
private final ChannelDataManager mChannelDataManager;
private final TvInputManagerHelper mInputManager;
private final Menu mMenu;
+ private final TunableTvView mTvView;
private final SideFragmentManager mSideFragmentManager;
private final ProgramGuide mProgramGuide;
+ private final ChannelBannerView mChannelBannerView;
private final KeypadChannelSwitchView mKeypadChannelSwitchView;
private final SelectInputView mSelectInputView;
private final ProgramGuideSearchFragment mSearchFragment;
@@ -185,6 +222,7 @@ public class TvOverlayManager {
private SafeDismissDialogFragment mCurrentDialog;
private boolean mSetupFragmentActive;
private boolean mNewSourcesFragmentActive;
+ private boolean mChannelBannerHiddenBySideFragment;
private final Handler mHandler = new TvOverlayHandler(this);
private @TvOverlayType int mOpenedOverlays;
@@ -195,15 +233,17 @@ public class TvOverlayManager {
private OnBackStackChangedListener mOnBackStackChangedListener;
public TvOverlayManager(MainActivity mainActivity, ChannelTuner channelTuner,
- TunableTvView tvView, KeypadChannelSwitchView keypadChannelSwitchView,
- ChannelBannerView channelBannerView, InputBannerView inputBannerView,
- SelectInputView selectInputView, ViewGroup sceneContainer,
- ProgramGuideSearchFragment searchFragment) {
+ TunableTvView tvView, TvOptionsManager optionsManager,
+ KeypadChannelSwitchView keypadChannelSwitchView, ChannelBannerView channelBannerView,
+ InputBannerView inputBannerView, SelectInputView selectInputView,
+ ViewGroup sceneContainer, ProgramGuideSearchFragment searchFragment) {
mMainActivity = mainActivity;
mChannelTuner = channelTuner;
ApplicationSingletons singletons = TvApplication.getSingletons(mainActivity);
mChannelDataManager = singletons.getChannelDataManager();
mInputManager = singletons.getTvInputManagerHelper();
+ mTvView = tvView;
+ mChannelBannerView = channelBannerView;
mKeypadChannelSwitchView = keypadChannelSwitchView;
mSelectInputView = selectInputView;
mSearchFragment = searchFragment;
@@ -225,7 +265,8 @@ public class TvOverlayManager {
});
// Menu
MenuView menuView = (MenuView) mainActivity.findViewById(R.id.menu);
- mMenu = new Menu(mainActivity, tvView, menuView, new MenuRowFactory(mainActivity, tvView),
+ mMenu = new Menu(mainActivity, tvView, optionsManager, menuView,
+ new MenuRowFactory(mainActivity, tvView),
new Menu.OnMenuVisibilityChangeListener() {
@Override
public void onMenuVisibilityChange(boolean visible) {
@@ -249,7 +290,7 @@ public class TvOverlayManager {
new Runnable() {
@Override
public void run() {
- mMainActivity.showChannelBannerIfHiddenBySideFragment();
+ showChannelBannerIfHiddenBySideFragment();
onOverlayClosed(OVERLAY_TYPE_SIDE_FRAGMENT);
}
});
@@ -320,6 +361,9 @@ public class TvOverlayManager {
public void release() {
mMenu.release();
mHandler.removeCallbacksAndMessages(null);
+ if (mKeypadChannelSwitchView != null) {
+ mKeypadChannelSwitchView.setChannels(null);
+ }
}
/**
@@ -436,6 +480,13 @@ public class TvOverlayManager {
onOverlayOpened(OVERLAY_TYPE_DIALOG);
}
+ /**
+ * Should be called by {@link MainActivity} when the currently browsable channels are updated.
+ */
+ public void onBrowsableChannelsUpdated() {
+ mKeypadChannelSwitchView.setChannels(mChannelTuner.getBrowsableChannelList());
+ }
+
private void runAfterSideFragmentsAreClosed(final Runnable runnable) {
if (mSideFragmentManager.isSidePanelVisible()) {
// When the side panel is closing, it closes all the fragments, so the new fragment
@@ -541,7 +592,7 @@ public class TvOverlayManager {
* Shows DVR manager.
*/
public void showDvrManager() {
- Intent intent = new Intent(mMainActivity, DvrActivity.class);
+ Intent intent = new Intent(mMainActivity, DvrBrowseActivity.class);
mMainActivity.startActivity(intent);
}
@@ -564,18 +615,34 @@ public class TvOverlayManager {
}
/**
+ * Shows DVR history dialog.
+ */
+ public void showDvrHistoryDialog() {
+ showDialogFragment(DvrHistoryDialogFragment.DIALOG_TAG,
+ new DvrHistoryDialogFragment(), false);
+ }
+
+ /**
* Shows banner view.
*/
public void showBanner() {
mTransitionManager.goToChannelBannerScene();
}
- public void showKeypadChannelSwitch() {
- hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
- | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
- mTransitionManager.goToKeypadChannelSwitchScene();
+ /**
+ * Pops up the KeypadChannelSwitchView with the given key input event.
+ *
+ * @param keyCode A key code of the key event.
+ */
+ public void showKeypadChannelSwitch(int keyCode) {
+ if (mChannelTuner.areAllChannelsLoaded()) {
+ hideOverlays(TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SCENE
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_DIALOG
+ | TvOverlayManager.FLAG_HIDE_OVERLAYS_KEEP_FRAGMENT);
+ mTransitionManager.goToKeypadChannelSwitchScene();
+ mKeypadChannelSwitchView.onNumberKeyUp(keyCode - KeyEvent.KEYCODE_0);
+ }
}
/**
@@ -619,6 +686,31 @@ public class TvOverlayManager {
}
/**
+ * Shows/hides the program guide according to it's hidden or shown now.
+ *
+ * @return {@code true} if program guide is going to be shown, otherwise {@code false}.
+ */
+ public boolean toggleProgramGuide() {
+ if (mProgramGuide.isActive()) {
+ mProgramGuide.onBackPressed();
+ return false;
+ } else {
+ showProgramGuide();
+ return true;
+ }
+ }
+
+ /**
+ * Sets blocking content rating of the currently playing TV channel.
+ */
+ public void setBlockingContentRating(TvContentRating rating) {
+ if (!mMainActivity.isChannelChangeKeyDownReceived()) {
+ mChannelBannerView.setBlockingContentRating(rating);
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK);
+ }
+ }
+
+ /**
* Hides all the opened overlays according to the flags.
*/
// TODO: Add test for this method.
@@ -631,12 +723,12 @@ public class TvOverlayManager {
} else {
if (mCurrentDialog != null) {
if (mCurrentDialog instanceof PinDialogFragment) {
- // The result listener of PinDialogFragment could call MenuView when
- // the dialog is dismissed. In order not to call it, set the result listener
- // to null.
- ((PinDialogFragment) mCurrentDialog).setResultListener(null);
+ // We don't want any OnPinCheckedListener is triggered to prevent any possible
+ // side effects. Dismisses the dialog silently.
+ ((PinDialogFragment) mCurrentDialog).dismissSilently();
+ } else {
+ mCurrentDialog.dismiss();
}
- mCurrentDialog.dismiss();
}
mPendingDialogActionQueue.clear();
mCurrentDialog = null;
@@ -674,7 +766,7 @@ public class TvOverlayManager {
}
if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANELS) != 0) {
// Keeps side panels.
- } else if (mSideFragmentManager.isSidePanelVisible()) {
+ } else if (mSideFragmentManager.isActive()) {
if ((flags & FLAG_HIDE_OVERLAYS_KEEP_SIDE_PANEL_HISTORY) != 0) {
mSideFragmentManager.hideSidePanel(withAnimation);
} else {
@@ -701,6 +793,83 @@ public class TvOverlayManager {
|| mNewSourcesFragmentActive;
}
+ /**
+ * Updates and shows channel banner if it's needed.
+ */
+ public void updateChannelBannerAndShowIfNeeded(@ChannelBannerUpdateReason int reason) {
+ if(DEBUG) Log.d(TAG, "updateChannelBannerAndShowIfNeeded(reason=" + reason + ")");
+ if (mMainActivity.isChannelChangeKeyDownReceived()
+ && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE
+ && reason != UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
+ // Tuning is still ongoing, no need to update banner for other reasons
+ return;
+ }
+ if (!mChannelTuner.isCurrentChannelPassthrough()) {
+ int lockType = ChannelBannerView.LOCK_NONE;
+ if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST) {
+ if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()
+ && mMainActivity.getCurrentChannel().isLocked()) {
+ lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
+ } else {
+ // Do not show detailed program information while fast-tuning.
+ lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
+ }
+ } else if (reason == UPDATE_CHANNEL_BANNER_REASON_TUNE) {
+ if (mMainActivity.getParentalControlSettings().isParentalControlsEnabled()) {
+ if (mMainActivity.getCurrentChannel().isLocked()) {
+ lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
+ } else {
+ // If parental control is turned on,
+ // assumes that program is locked by default and waits for onContentAllowed.
+ lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
+ }
+ }
+ } else if (mTvView.isScreenBlocked()) {
+ lockType = ChannelBannerView.LOCK_CHANNEL_INFO;
+ } else if (mTvView.isContentBlocked() || (mMainActivity.getParentalControlSettings()
+ .isParentalControlsEnabled() && !mTvView.isVideoOrAudioAvailable())) {
+ // If the parental control is enabled, do not show the program detail until the
+ // video becomes available.
+ lockType = ChannelBannerView.LOCK_PROGRAM_DETAIL;
+ }
+ // If lock type is not changed, we don't need to update channel banner by parental
+ // control.
+ int previousLockType = mChannelBannerView.setLockType(lockType);
+ if (previousLockType == lockType
+ && reason == UPDATE_CHANNEL_BANNER_REASON_LOCK_OR_UNLOCK) {
+ return;
+ } else if (reason == UPDATE_CHANNEL_BANNER_REASON_UPDATE_STREAM_INFO) {
+ mChannelBannerView.updateStreamInfo(mTvView);
+ // If parental control is enabled, we shows program description when the video is
+ // available, instead of tuning. Therefore we need to check it here if the program
+ // description is previously hidden by parental control.
+ if (previousLockType == ChannelBannerView.LOCK_PROGRAM_DETAIL &&
+ lockType != ChannelBannerView.LOCK_PROGRAM_DETAIL) {
+ mChannelBannerView.updateViews(false);
+ }
+ } else {
+ mChannelBannerView.updateViews(reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
+ || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
+ }
+ }
+ boolean needToShowBanner = (reason == UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW
+ || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE
+ || reason == UPDATE_CHANNEL_BANNER_REASON_TUNE_FAST);
+ if (needToShowBanner && !mMainActivity.willShowOverlayUiWhenResume()
+ && getCurrentDialog() == null
+ && !isSetupFragmentActive()
+ && !isNewSourcesFragmentActive()) {
+ if (mChannelTuner.getCurrentChannel() == null) {
+ mChannelBannerHiddenBySideFragment = false;
+ } else if (getSideFragmentManager().isActive()) {
+ mChannelBannerHiddenBySideFragment = true;
+ } else {
+ mChannelBannerHiddenBySideFragment = false;
+ showBanner();
+ }
+ }
+ }
+
@TvOverlayType private int convertSceneToOverlayType(@SceneType int sceneType) {
switch (sceneType) {
case TvTransitionManager.SCENE_TYPE_CHANNEL_BANNER:
@@ -749,6 +918,18 @@ public class TvOverlayManager {
}
}
+ /**
+ * Shows the channel banner if it was hidden from the side fragment.
+ *
+ * <p>When the side fragment is visible, showing the channel banner should be put off until the
+ * side fragment is closed even though the channel changes.
+ */
+ private void showChannelBannerIfHiddenBySideFragment() {
+ if (mChannelBannerHiddenBySideFragment) {
+ updateChannelBannerAndShowIfNeeded(UPDATE_CHANNEL_BANNER_REASON_FORCE_SHOW);
+ }
+ }
+
private String toBinaryString(int value) {
return String.format("0b%" + NUM_OVERLAY_TYPES + "s", Integer.toBinaryString(value))
.replace(' ', '0');
@@ -843,6 +1024,7 @@ public class TvOverlayManager {
timeShiftManager.play();
showMenu(Menu.REASON_PLAY_CONTROLS_PLAY);
break;
+ case KeyEvent.KEYCODE_MEDIA_STOP:
case KeyEvent.KEYCODE_MEDIA_PAUSE:
timeShiftManager.pause();
showMenu(Menu.REASON_PLAY_CONTROLS_PAUSE);
@@ -916,7 +1098,7 @@ public class TvOverlayManager {
}
if (mMenu.isActive()) {
if (KeypadChannelSwitchView.isChannelNumberKey(keyCode)) {
- mMainActivity.showKeypadChannelSwitchView(keyCode);
+ showKeypadChannelSwitch(keyCode);
return MainActivity.KEY_EVENT_HANDLER_RESULT_HANDLED;
}
return MainActivity.KEY_EVENT_HANDLER_RESULT_DISPATCH_TO_OVERLAY;
@@ -971,6 +1153,7 @@ public class TvOverlayManager {
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
case KeyEvent.KEYCODE_MEDIA_SKIP_FORWARD:
case KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
return true;
}
return false;
diff --git a/src/com/android/tv/ui/TvTransitionManager.java b/src/com/android/tv/ui/TvTransitionManager.java
index 52e96cc0..628bbb72 100644
--- a/src/com/android/tv/ui/TvTransitionManager.java
+++ b/src/com/android/tv/ui/TvTransitionManager.java
@@ -129,8 +129,8 @@ public class TvTransitionManager extends TransitionManager {
public void goToSelectInputScene() {
initIfNeeded();
if (mCurrentScene != mSelectInputScene) {
- transitionTo(mSelectInputScene);
mSelectInputView.setCurrentChannel(mMainActivity.getCurrentChannel());
+ transitionTo(mSelectInputScene);
}
}
diff --git a/src/com/android/tv/ui/TvViewUiManager.java b/src/com/android/tv/ui/TvViewUiManager.java
index bf874fc7..f042987a 100644
--- a/src/com/android/tv/ui/TvViewUiManager.java
+++ b/src/com/android/tv/ui/TvViewUiManager.java
@@ -24,21 +24,19 @@ import android.animation.TimeInterpolator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Point;
import android.hardware.display.DisplayManager;
+import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.util.Property;
import android.view.Display;
-import android.view.Gravity;
-import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
@@ -52,9 +50,8 @@ import com.android.tv.data.DisplayMode;
import com.android.tv.util.TvSettings;
/**
- * The TvViewUiManager is responsible for handling UI layouting and animation of main and PIP
- * TvViews. It also control the settings regarding TvView UI such as display mode, PIP layout,
- * and PIP size.
+ * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView.
+ * It also control the settings regarding TvView UI such as display mode.
*/
public class TvViewUiManager {
private static final String TAG = "TvViewManager";
@@ -69,42 +66,44 @@ public class TvViewUiManager {
private final Resources mResources;
private final FrameLayout mContentView;
private final TunableTvView mTvView;
- private final TunableTvView mPipView;
private final TvOptionsManager mTvOptionsManager;
- private final int mTvViewPapWidth;
private final int mTvViewShrunkenStartMargin;
private final int mTvViewShrunkenEndMargin;
- private final int mTvViewPapStartMargin;
- private final int mTvViewPapEndMargin;
private int mWindowWidth;
private int mWindowHeight;
- private final int mPipViewHorizontalMargin;
- private final int mPipViewTopMargin;
- private final int mPipViewBottomMargin;
private final SharedPreferences mSharedPreferences;
private final TimeInterpolator mLinearOutSlowIn;
private final TimeInterpolator mFastOutLinearIn;
- private final Handler mHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch(msg.what) {
- case MSG_SET_LAYOUT_PARAMS:
- FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) msg.obj;
- if (DEBUG) {
- Log.d(TAG, "setFixedSize: w=" + layoutParams.width + " h="
- + layoutParams.height);
- }
- mTvView.setLayoutParams(layoutParams);
- // Smooth PIP size change, we don't change surface size when
- // isInPictureInPictureMode is true.
- if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext)
- || !((Activity) mContext).isInPictureInPictureMode()) {
- mTvView.setFixedSurfaceSize(layoutParams.width, layoutParams.height);
+ private final Handler mHandler =
+ new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SET_LAYOUT_PARAMS:
+ FrameLayout.LayoutParams layoutParams =
+ (FrameLayout.LayoutParams) msg.obj;
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "setFixedSize: w="
+ + layoutParams.width
+ + " h="
+ + layoutParams.height);
+ }
+ mTvView.setTvViewLayoutParams(layoutParams);
+ mTvView.setLayoutParams(mTvViewFrame);
+ // Smooth PIP size change, we don't change surface size when
+ // isInPictureInPictureMode is true.
+ if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext)
+ || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+ && !((Activity) mContext).isInPictureInPictureMode())) {
+ mTvView.setFixedSurfaceSize(
+ layoutParams.width, layoutParams.height);
+ }
+ break;
}
- break;
- }
- }
- };
+ }
+ };
private int mDisplayMode;
// Used to restore the previous state from ShrunkenTvView state.
private int mTvViewStartMarginBeforeShrunken;
@@ -113,16 +112,13 @@ public class TvViewUiManager {
private boolean mIsUnderShrunkenTvView;
private int mTvViewStartMargin;
private int mTvViewEndMargin;
- private int mPipLayout;
- private int mPipSize;
- private boolean mPipStarted;
private ObjectAnimator mTvViewAnimator;
private FrameLayout.LayoutParams mTvViewLayoutParams;
// TV view's position when the display mode is FULL. It is used to compute PIP location relative
// to TV view's position.
- private MarginLayoutParams mTvViewFrame;
- private MarginLayoutParams mLastAnimatedTvViewFrame;
- private MarginLayoutParams mOldTvViewFrame;
+ private FrameLayout.LayoutParams mTvViewFrame;
+ private FrameLayout.LayoutParams mLastAnimatedTvViewFrame;
+ private FrameLayout.LayoutParams mOldTvViewFrame;
private ObjectAnimator mBackgroundAnimator;
private int mBackgroundColor;
private int mAppliedDisplayedMode = DisplayMode.MODE_NOT_DEFINED;
@@ -130,12 +126,11 @@ public class TvViewUiManager {
private int mAppliedTvViewEndMargin;
private float mAppliedVideoDisplayAspectRatio;
- public TvViewUiManager(Context context, TunableTvView tvView, TunableTvView pipView,
+ public TvViewUiManager(Context context, TunableTvView tvView,
FrameLayout contentView, TvOptionsManager tvOptionManager) {
mContext = context;
mResources = mContext.getResources();
mTvView = tvView;
- mPipView = pipView;
mContentView = contentView;
mTvOptionsManager = tvOptionManager;
@@ -147,18 +142,12 @@ public class TvViewUiManager {
mWindowWidth = size.x;
mWindowHeight = size.y;
- // Have an assumption that PIP and TvView Shrinking happens only in full screen.
+ // Have an assumption that TvView Shrinking happens only in full screen.
mTvViewShrunkenStartMargin = mResources
.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start);
mTvViewShrunkenEndMargin =
mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end)
+ mResources.getDimensionPixelSize(R.dimen.side_panel_width);
- int papMarginHorizontal = mResources
- .getDimensionPixelOffset(R.dimen.papview_margin_horizontal);
- int papSpacing = mResources.getDimensionPixelOffset(R.dimen.papview_spacing);
- mTvViewPapWidth = (mWindowWidth - papSpacing) / 2 - papMarginHorizontal;
- mTvViewPapStartMargin = papMarginHorizontal + mTvViewPapWidth + papSpacing;
- mTvViewPapEndMargin = papMarginHorizontal;
mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -167,11 +156,6 @@ public class TvViewUiManager {
.loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
mFastOutLinearIn = AnimationUtils
.loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in);
-
- mPipViewHorizontalMargin = mResources
- .getDimensionPixelOffset(R.dimen.pipview_margin_horizontal);
- mPipViewTopMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_top);
- mPipViewBottomMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_bottom);
}
public void onConfigurationChanged(final int windowWidth, final int windowHeight) {
@@ -200,18 +184,11 @@ public class TvViewUiManager {
*/
public void startShrunkenTvView() {
mIsUnderShrunkenTvView = true;
+ mTvView.setIsUnderShrunken(true);
mTvViewStartMarginBeforeShrunken = mTvViewStartMargin;
mTvViewEndMarginBeforeShrunken = mTvViewEndMargin;
- if (mPipStarted && getPipLayout() == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
- float sidePanelWidth = mResources.getDimensionPixelOffset(R.dimen.side_panel_width);
- float factor = 1.0f - sidePanelWidth / mWindowWidth;
- int startMargin = (int) (mTvViewPapStartMargin * factor);
- int endMargin = (int) (mTvViewPapEndMargin * factor + sidePanelWidth);
- setTvViewMargin(startMargin, endMargin);
- } else {
- setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
- }
+ setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true);
}
@@ -221,6 +198,7 @@ public class TvViewUiManager {
*/
public void endShrunkenTvView() {
mIsUnderShrunkenTvView = false;
+ mTvView.setIsUnderShrunken(false);
setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken);
setDisplayMode(mDisplayModeBeforeShrunken, false, true);
}
@@ -296,9 +274,9 @@ public class TvViewUiManager {
}
/**
- * Updates TvView. It is called when video resolution is updated.
+ * Updates TvView's aspect ratio. It should be called when video resolution is changed.
*/
- public void updateTvView() {
+ public void updateTvAspectRatio() {
applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false);
if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) {
mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
@@ -327,120 +305,6 @@ public class TvViewUiManager {
}
/**
- * Returns the current PIP layout. The layout should be one of
- * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT},
- * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and
- * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}.
- */
- public int getPipLayout() {
- return mPipLayout;
- }
-
- /**
- * Sets the PIP layout. The layout should be one of
- * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT},
- * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and
- * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}.
- *
- * @param storeInPreference if true, the stored value will be restored by
- * {@link #restorePipLayout()}.
- */
- public void setPipLayout(int pipLayout, boolean storeInPreference) {
- mPipLayout = pipLayout;
- if (storeInPreference) {
- TvSettings.setPipLayout(mContext, pipLayout);
- }
- updatePipView(mTvViewFrame);
- if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
- setTvViewMargin(mTvViewPapStartMargin, mTvViewPapEndMargin);
- setDisplayMode(DisplayMode.MODE_NORMAL, false, false);
- } else {
- setTvViewMargin(0, 0);
- restoreDisplayMode(false);
- }
- mTvOptionsManager.onPipLayoutChanged(pipLayout);
- }
-
- /**
- * Restores the PIP layout which {@link #setPipLayout} lastly stores.
- */
- public void restorePipLayout() {
- setPipLayout(TvSettings.getPipLayout(mContext), false);
- }
-
- /**
- * Called when PIP is started.
- */
- public void onPipStart() {
- mPipStarted = true;
- updatePipView();
- mPipView.setVisibility(View.VISIBLE);
- }
-
- /**
- * Called when PIP is stopped.
- */
- public void onPipStop() {
- setTvViewMargin(0, 0);
- mPipView.setVisibility(View.GONE);
- mPipStarted = false;
- }
-
- /**
- * Called when PIP is resumed.
- */
- public void showPipForResume() {
- mPipView.setVisibility(View.VISIBLE);
- }
-
- /**
- * Called when PIP is paused.
- */
- public void hidePipForPause() {
- if (mPipLayout != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
- mPipView.setVisibility(View.GONE);
- }
- }
-
- /**
- * Updates PIP view. It is usually called, when video resolution in PIP is updated.
- */
- public void updatePipView() {
- updatePipView(mTvViewFrame);
- }
-
- /**
- * Returns the size of the PIP view.
- */
- public int getPipSize() {
- return mPipSize;
- }
-
- /**
- * Sets PIP size and applies it immediately.
- *
- * @param pipSize PIP size. The value should be one of {@link TvSettings#PIP_SIZE_BIG}
- * and {@link TvSettings#PIP_SIZE_SMALL}.
- * @param storeInPreference if true, the stored value will be restored by
- * {@link #restorePipSize()}.
- */
- public void setPipSize(int pipSize, boolean storeInPreference) {
- mPipSize = pipSize;
- if (storeInPreference) {
- TvSettings.setPipSize(mContext, pipSize);
- }
- updatePipView(mTvViewFrame);
- mTvOptionsManager.onPipSizeChanged(pipSize);
- }
-
- /**
- * Restores the PIP size which {@link #setPipSize} lastly stores.
- */
- public void restorePipSize() {
- setPipSize(TvSettings.getPipSize(mContext), false);
- }
-
- /**
* This margins will be applied when applyDisplayMode is called.
*/
private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
@@ -488,14 +352,14 @@ public class TvViewUiManager {
}
private void setTvViewPosition(final FrameLayout.LayoutParams layoutParams,
- MarginLayoutParams tvViewFrame, boolean animate) {
+ FrameLayout.LayoutParams tvViewFrame, boolean animate) {
if (DEBUG) {
Log.d(TAG, "setTvViewPosition: w=" + layoutParams.width + " h=" + layoutParams.height
+ " s=" + layoutParams.getMarginStart() + " t=" + layoutParams.topMargin
+ " e=" + layoutParams.getMarginEnd() + " b=" + layoutParams.bottomMargin
+ " animate=" + animate);
}
- MarginLayoutParams oldTvViewFrame = mTvViewFrame;
+ FrameLayout.LayoutParams oldTvViewFrame = mTvViewFrame;
mTvViewLayoutParams = layoutParams;
mTvViewFrame = tvViewFrame;
if (animate) {
@@ -503,11 +367,11 @@ public class TvViewUiManager {
if (mTvViewAnimator.isStarted()) {
// Cancel the current animation and start new one.
mTvViewAnimator.cancel();
- mOldTvViewFrame = mLastAnimatedTvViewFrame;
+ mOldTvViewFrame = new FrameLayout.LayoutParams(mLastAnimatedTvViewFrame);
} else {
- mOldTvViewFrame = oldTvViewFrame;
+ mOldTvViewFrame = new FrameLayout.LayoutParams(oldTvViewFrame);
}
- mTvViewAnimator.setObjectValues(mTvView.getLayoutParams(), layoutParams);
+ mTvViewAnimator.setObjectValues(mTvView.getTvViewLayoutParams(), layoutParams);
mTvViewAnimator.setEvaluator(new TypeEvaluator<FrameLayout.LayoutParams>() {
FrameLayout.LayoutParams lp;
@Override
@@ -517,7 +381,7 @@ public class TvViewUiManager {
lp = new FrameLayout.LayoutParams(0, 0);
lp.gravity = startValue.gravity;
}
- interpolateMarginsRelative(lp, startValue, endValue, fraction);
+ interpolateMargins(lp, startValue, endValue, fraction);
return lp;
}
});
@@ -538,116 +402,10 @@ public class TvViewUiManager {
mHandler.removeMessages(MSG_SET_LAYOUT_PARAMS);
mHandler.obtainMessage(MSG_SET_LAYOUT_PARAMS, layoutParams).sendToTarget();
} else {
- mTvView.setLayoutParams(layoutParams);
- }
- updatePipView(mTvViewFrame);
- }
- }
-
- /**
- * The redlines assume that the ratio of the TV screen is 16:9. If the radio is not 16:9, the
- * layout of PAP can be broken.
- */
- @SuppressLint("RtlHardcoded")
- private void updatePipView(MarginLayoutParams tvViewFrame) {
- if (!mPipStarted) {
- return;
- }
- int width;
- int height;
- int startMargin;
- int endMargin;
- int topMargin;
- int bottomMargin;
- int gravity;
-
- if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
- gravity = Gravity.CENTER_VERTICAL | Gravity.START;
- height = tvViewFrame.height;
- float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio();
- if (videoDisplayAspectRatio <= 0f) {
- width = tvViewFrame.width;
- } else {
- width = (int) (height * videoDisplayAspectRatio);
- if (width > tvViewFrame.width) {
- width = tvViewFrame.width;
- }
- }
- startMargin = mResources.getDimensionPixelOffset(R.dimen.papview_margin_horizontal)
- * tvViewFrame.width / mTvViewPapWidth + (tvViewFrame.width - width) / 2;
- endMargin = 0;
- topMargin = 0;
- bottomMargin = 0;
- } else {
- int tvViewWidth = tvViewFrame.width;
- int tvViewHeight = tvViewFrame.height;
- int tvStartMargin = tvViewFrame.getMarginStart();
- int tvEndMargin = tvViewFrame.getMarginEnd();
- int tvTopMargin = tvViewFrame.topMargin;
- int tvBottomMargin = tvViewFrame.bottomMargin;
- float horizontalScaleFactor = (float) tvViewWidth / mWindowWidth;
- float verticalScaleFactor = (float) tvViewHeight / mWindowHeight;
-
- int maxWidth;
- if (mPipSize == TvSettings.PIP_SIZE_SMALL) {
- maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_width)
- * horizontalScaleFactor);
- height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_height)
- * verticalScaleFactor);
- } else if (mPipSize == TvSettings.PIP_SIZE_BIG) {
- maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_width)
- * horizontalScaleFactor);
- height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_height)
- * verticalScaleFactor);
- } else {
- throw new IllegalArgumentException("Invalid PIP size: " + mPipSize);
- }
- float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio();
- if (videoDisplayAspectRatio <= 0f) {
- width = maxWidth;
- } else {
- width = (int) (height * videoDisplayAspectRatio);
- if (width > maxWidth) {
- width = maxWidth;
- }
- }
-
- startMargin = tvStartMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor);
- endMargin = tvEndMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor);
- topMargin = tvTopMargin + (int) (mPipViewTopMargin * verticalScaleFactor);
- bottomMargin = tvBottomMargin + (int) (mPipViewBottomMargin * verticalScaleFactor);
-
- switch (mPipLayout) {
- case TvSettings.PIP_LAYOUT_TOP_LEFT:
- gravity = Gravity.TOP | Gravity.LEFT;
- break;
- case TvSettings.PIP_LAYOUT_TOP_RIGHT:
- gravity = Gravity.TOP | Gravity.RIGHT;
- break;
- case TvSettings.PIP_LAYOUT_BOTTOM_LEFT:
- gravity = Gravity.BOTTOM | Gravity.LEFT;
- break;
- case TvSettings.PIP_LAYOUT_BOTTOM_RIGHT:
- gravity = Gravity.BOTTOM | Gravity.RIGHT;
- break;
- default:
- throw new IllegalArgumentException("Invalid PIP location: " + mPipLayout);
+ mTvView.setTvViewLayoutParams(layoutParams);
+ mTvView.setLayoutParams(mTvViewFrame);
}
}
-
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPipView.getLayoutParams();
- if (lp.width != width || lp.height != height || lp.getMarginStart() != startMargin
- || lp.getMarginEnd() != endMargin || lp.topMargin != topMargin
- || lp.bottomMargin != bottomMargin || lp.gravity != gravity) {
- lp.width = width;
- lp.height = height;
- lp.setMarginStart(startMargin);
- lp.setMarginEnd(endMargin);
- lp.topMargin = topMargin;
- lp.bottomMargin = bottomMargin;
- lp.gravity = gravity;
- mPipView.setLayoutParams(lp);
- }
}
private void initTvAnimatorIfNeeded() {
@@ -663,7 +421,7 @@ public class TvViewUiManager {
// because TvView may request layout itself during animation and layout SurfaceView with
// its own parameters when TvInputService requests to do so.
mTvViewAnimator = new ObjectAnimator();
- mTvViewAnimator.setTarget(mTvView);
+ mTvViewAnimator.setTarget(mTvView.getTvView());
mTvViewAnimator.setProperty(
Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams"));
mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration));
@@ -693,10 +451,10 @@ public class TvViewUiManager {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
float fraction = animator.getAnimatedFraction();
- mLastAnimatedTvViewFrame = new MarginLayoutParams(0, 0);
- interpolateMarginsRelative(mLastAnimatedTvViewFrame,
+ mLastAnimatedTvViewFrame = (FrameLayout.LayoutParams) mTvView.getLayoutParams();
+ interpolateMargins(mLastAnimatedTvViewFrame,
mOldTvViewFrame, mTvViewFrame, fraction);
- updatePipView(mLastAnimatedTvViewFrame);
+ mTvView.setLayoutParams(mLastAnimatedTvViewFrame);
}
});
}
@@ -745,66 +503,58 @@ public class TvViewUiManager {
}
int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin;
int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth;
- FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0,
- ((FrameLayout.LayoutParams) mTvView.getLayoutParams()).gravity);
int displayMode = mDisplayMode;
- double availableAreaRatio = 0;
- double videoRatio = 0;
+ float availableAreaRatio = 0;
if (availableAreaWidth <= 0 || availableAreaHeight <= 0) {
displayMode = DisplayMode.MODE_FULL;
Log.w(TAG, "Some resolution info is missing during applyDisplayMode. ("
+ "availableAreaWidth=" + availableAreaWidth + ", availableAreaHeight="
+ availableAreaHeight + ")");
} else {
- availableAreaRatio = (double) availableAreaWidth / availableAreaHeight;
- videoRatio = videoDisplayAspectRatio;
+ availableAreaRatio = (float) availableAreaWidth / availableAreaHeight;
}
-
- int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2;
- MarginLayoutParams tvViewFrame = createMarginLayoutParams(
- mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
- layoutParams.width = availableAreaWidth;
- layoutParams.height = availableAreaHeight;
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0,
+ ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity);
switch (displayMode) {
- case DisplayMode.MODE_FULL:
- layoutParams.width = availableAreaWidth;
- layoutParams.height = availableAreaHeight;
- break;
case DisplayMode.MODE_ZOOM:
- if (videoRatio < availableAreaRatio) {
+ if (videoDisplayAspectRatio < availableAreaRatio) {
// Y axis will be clipped.
layoutParams.width = availableAreaWidth;
- layoutParams.height = (int) Math.round(availableAreaWidth / videoRatio);
+ layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio);
} else {
// X axis will be clipped.
- layoutParams.width = (int) Math.round(availableAreaHeight * videoRatio);
+ layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio);
layoutParams.height = availableAreaHeight;
}
break;
case DisplayMode.MODE_NORMAL:
- if (videoRatio < availableAreaRatio) {
+ if (videoDisplayAspectRatio < availableAreaRatio) {
// X axis has black area.
- layoutParams.width = (int) Math.round(availableAreaHeight * videoRatio);
+ layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio);
layoutParams.height = availableAreaHeight;
} else {
// Y axis has black area.
layoutParams.width = availableAreaWidth;
- layoutParams.height = (int) Math.round(availableAreaWidth / videoRatio);
+ layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio);
}
break;
+ case DisplayMode.MODE_FULL:
+ default:
+ layoutParams.width = availableAreaWidth;
+ layoutParams.height = availableAreaHeight;
+ break;
}
-
// FrameLayout has an issue with centering when left and right margins differ.
// So stick to Gravity.START | Gravity.CENTER_VERTICAL.
- int marginStart = mTvViewStartMargin + (availableAreaWidth - layoutParams.width) / 2;
+ int marginStart = (availableAreaWidth - layoutParams.width) / 2;
layoutParams.setMarginStart(marginStart);
- // Set marginEnd as well because setTvViewPosition uses both start/end margin.
- layoutParams.setMarginEnd(mWindowWidth - layoutParams.width - marginStart);
-
+ int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2;
+ FrameLayout.LayoutParams tvViewFrame = createMarginLayoutParams(
+ mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
+ setTvViewPosition(layoutParams, tvViewFrame, animate);
setBackgroundColor(mResources.getColor(isTvViewFullScreen()
? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview,
- null), layoutParams, animate);
- setTvViewPosition(layoutParams, tvViewFrame, animate);
+ null), layoutParams, animate);
// Update the current display mode.
mTvOptionsManager.onDisplayModeChanged(displayMode);
@@ -814,7 +564,7 @@ public class TvViewUiManager {
return (int) (start + (end - start) * fraction);
}
- private static void interpolateMarginsRelative(MarginLayoutParams out,
+ private static void interpolateMargins(MarginLayoutParams out,
MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction) {
out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction);
out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction);
@@ -825,9 +575,9 @@ public class TvViewUiManager {
out.height = interpolate(startValue.height, endValue.height, fraction);
}
- private MarginLayoutParams createMarginLayoutParams(
+ private FrameLayout.LayoutParams createMarginLayoutParams(
int startMargin, int endMargin, int topMargin, int bottomMargin) {
- MarginLayoutParams lp = new MarginLayoutParams(0, 0);
+ FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(0, 0);
lp.setMarginStart(startMargin);
lp.setMarginEnd(endMargin);
lp.topMargin = topMargin;
@@ -836,4 +586,4 @@ public class TvViewUiManager {
lp.height = mWindowHeight - topMargin - bottomMargin;
return lp;
}
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/ActionItem.java b/src/com/android/tv/ui/sidepanel/ActionItem.java
index 23aff91c..cd70a886 100644
--- a/src/com/android/tv/ui/sidepanel/ActionItem.java
+++ b/src/com/android/tv/ui/sidepanel/ActionItem.java
@@ -17,7 +17,6 @@
package com.android.tv.ui.sidepanel;
import android.view.View;
-import android.widget.ImageView;
import android.widget.TextView;
import com.android.tv.R;
@@ -25,24 +24,14 @@ import com.android.tv.R;
public abstract class ActionItem extends Item {
private final String mTitle;
private final String mDescription;
- private final int mIconId;
public ActionItem(String title) {
- this(title, null, 0);
+ this(title, null);
}
public ActionItem(String title, String description) {
- this(title, description, 0);
- }
-
- public ActionItem(String title, int iconId) {
- this(title, null, iconId);
- }
-
- public ActionItem(String title, String description, int iconId) {
mTitle = title;
mDescription = description;
- mIconId = iconId;
}
@Override
@@ -62,12 +51,5 @@ public abstract class ActionItem extends Item {
} else {
descriptionView.setVisibility(View.GONE);
}
- ImageView iconView = (ImageView) view.findViewById(R.id.icon);
- if (mIconId != 0) {
- iconView.setVisibility(View.VISIBLE);
- iconView.setImageResource(mIconId);
- } else {
- iconView.setVisibility(View.GONE);
- }
}
} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
index d6ccdf6b..341e4350 100644
--- a/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/ClosedCaptionFragment.java
@@ -18,6 +18,7 @@ package com.android.tv.ui.sidepanel;
import android.media.tv.TvTrackInfo;
import android.os.Bundle;
+import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -25,7 +26,6 @@ import android.view.ViewGroup;
import com.android.tv.R;
import com.android.tv.util.CaptionSettings;
-import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -38,8 +38,6 @@ public class ClosedCaptionFragment extends SideFragment {
private String mClosedCaptionLanguage;
private String mClosedCaptionTrackId;
private ClosedCaptionOptionItem mSelectedItem;
- private List<Item> mItems;
- private boolean mPaused;
public ClosedCaptionFragment() {
super(KeyEvent.KEYCODE_CAPTIONS, KeyEvent.KEYCODE_S);
@@ -63,37 +61,32 @@ public class ClosedCaptionFragment extends SideFragment {
mClosedCaptionLanguage = captionSettings.getLanguage();
mClosedCaptionTrackId = captionSettings.getTrackId();
- mItems = new ArrayList<>();
+ List<Item> items = new ArrayList<>();
mSelectedItem = null;
List<TvTrackInfo> tracks = getMainActivity().getTracks(TvTrackInfo.TYPE_SUBTITLE);
if (tracks != null && !tracks.isEmpty()) {
- String trackId = captionSettings.isEnabled() ?
+ String selectedTrackId = captionSettings.isEnabled() ?
getMainActivity().getSelectedTrack(TvTrackInfo.TYPE_SUBTITLE) : null;
- boolean isEnabled = trackId != null;
-
- ClosedCaptionOptionItem item = new ClosedCaptionOptionItem(
- getString(R.string.closed_caption_option_item_off),
- CaptionSettings.OPTION_OFF, null, null);
- // Pick 'Off' as default because we may fail to find the matching language.
- mSelectedItem = item;
- if (!isEnabled) {
+ ClosedCaptionOptionItem item = new ClosedCaptionOptionItem(null, null);
+ items.add(item);
+ if (selectedTrackId == null) {
+ mSelectedItem = item;
item.setChecked(true);
+ setSelectedPosition(0);
}
- mItems.add(item);
-
- for (final TvTrackInfo track : tracks) {
- item = new ClosedCaptionOptionItem(getLabel(track),
- CaptionSettings.OPTION_ON, track.getId(), track.getLanguage());
- if (isEnabled && track.getId().equals(trackId)) {
- item.setChecked(true);
+ for (int i = 0; i < tracks.size(); i++) {
+ item = new ClosedCaptionOptionItem(tracks.get(i), i);
+ if (TextUtils.equals(selectedTrackId, tracks.get(i).getId())) {
mSelectedItem = item;
+ item.setChecked(true);
+ setSelectedPosition(i + 1);
}
- mItems.add(item);
+ items.add(item);
}
}
if (getMainActivity().hasCaptioningSettingsActivity()) {
- mItems.add(new ActionItem(getString(R.string.closed_caption_system_settings),
+ items.add(new ActionItem(getString(R.string.closed_caption_system_settings),
getString(R.string.closed_caption_system_settings_description)) {
@Override
protected void onSelected() {
@@ -103,14 +96,14 @@ public class ClosedCaptionFragment extends SideFragment {
@Override
protected void onFocused() {
super.onFocused();
- if (!mPaused && mSelectedItem != null) {
+ if (mSelectedItem != null) {
getMainActivity().selectSubtitleTrack(
mSelectedItem.mOption, mSelectedItem.mTrackId);
}
}
});
}
- return mItems;
+ return items;
}
@Override
@@ -120,50 +113,6 @@ public class ClosedCaptionFragment extends SideFragment {
}
@Override
- public void onResume() {
- super.onResume();
- if (mPaused) {
- // Apply system's closed caption settings to the UI.
- CaptionSettings captionSettings = getMainActivity().getCaptionSettings();
- mClosedCaptionOption = CaptionSettings.OPTION_SYSTEM;
- mClosedCaptionLanguage = captionSettings.getSystemLanguage();
- ClosedCaptionOptionItem selectedItem = null;
- if (captionSettings.isSystemSettingEnabled()) {
- for (Item item : mItems) {
- if (!(item instanceof ClosedCaptionOptionItem)) {
- continue;
- }
- ClosedCaptionOptionItem captionItem = (ClosedCaptionOptionItem) item;
- if (Utils.isEqualLanguage(captionItem.mLanguage, mClosedCaptionLanguage)) {
- selectedItem = captionItem;
- break;
- }
- }
- }
- if (mSelectedItem != null) {
- mSelectedItem.setChecked(false);
- }
- if (selectedItem == null && mItems.get(0) instanceof ClosedCaptionOptionItem) {
- selectedItem = (ClosedCaptionOptionItem) mItems.get(0);
- }
- if (selectedItem != null) {
- selectedItem.setChecked(true);
- }
- // We shouldn't call MainActivity.selectSubtitleTrack() here because
- // 1. Tracks are not available because video is just started at this moment.
- // 2. MainActivity will apply system settings when video's tracks are available.
- mSelectedItem = selectedItem;
- }
- mPaused = false;
- }
-
- @Override
- public void onPause() {
- super.onPause();
- mPaused = true;
- }
-
- @Override
public void onDestroyView() {
if (mResetClosedCaption) {
getMainActivity().selectSubtitleLanguage(mClosedCaptionOption, mClosedCaptionLanguage,
@@ -172,23 +121,28 @@ public class ClosedCaptionFragment extends SideFragment {
super.onDestroyView();
}
- private String getLabel(TvTrackInfo track) {
- if (track.getLanguage() != null) {
+ private String getLabel(TvTrackInfo track, Integer trackIndex) {
+ if (track == null) {
+ return getString(R.string.closed_caption_option_item_off);
+ } else if (track.getLanguage() != null) {
return new Locale(track.getLanguage()).getDisplayName();
}
- return getString(R.string.default_language);
+ return getString(R.string.closed_caption_unknown_language, trackIndex + 1);
}
private class ClosedCaptionOptionItem extends RadioButtonItem {
private final int mOption;
private final String mTrackId;
- private final String mLanguage;
- private ClosedCaptionOptionItem(String title, int option, String trackId, String language) {
- super(title);
- mOption = option;
- mTrackId = trackId;
- mLanguage = language;
+ private ClosedCaptionOptionItem(TvTrackInfo track, Integer trackIndex) {
+ super(getLabel(track, trackIndex));
+ if (track == null) {
+ mOption = CaptionSettings.OPTION_OFF;
+ mTrackId = null;
+ } else {
+ mOption = CaptionSettings.OPTION_ON;
+ mTrackId = track.getId();
+ }
}
@Override
diff --git a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
index 7613a9a2..c2746937 100644
--- a/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
+++ b/src/com/android/tv/ui/sidepanel/CompoundButtonItem.java
@@ -23,9 +23,12 @@ import android.widget.TextView;
import com.android.tv.R;
public abstract class CompoundButtonItem extends Item {
+ private static int sDefaultMaxLine = 0;
+
private final String mCheckedTitle;
private final String mUncheckedTitle;
private final String mDescription;
+ private final int mMaxLine;
private boolean mChecked;
private TextView mTextView;
private CompoundButton mCompoundButton;
@@ -38,6 +41,15 @@ public abstract class CompoundButtonItem extends Item {
mCheckedTitle = checkedTitle;
mUncheckedTitle = uncheckedTitle;
mDescription = description;
+ mMaxLine = 0;
+ }
+
+ public CompoundButtonItem(String checkedTitle, String uncheckedTitle, String description,
+ int maxLine) {
+ mCheckedTitle = checkedTitle;
+ mUncheckedTitle = uncheckedTitle;
+ mDescription = description;
+ mMaxLine = maxLine;
}
protected abstract int getCompoundButtonId();
@@ -57,6 +69,15 @@ public abstract class CompoundButtonItem extends Item {
mTextView = (TextView) view.findViewById(getTitleViewId());
TextView descriptionView = (TextView) view.findViewById(getDescriptionViewId());
if (mDescription != null) {
+ if (mMaxLine != 0) {
+ descriptionView.setMaxLines(mMaxLine);
+ } else {
+ if (sDefaultMaxLine == 0) {
+ sDefaultMaxLine = view.getContext().getResources()
+ .getInteger(R.integer.option_item_description_max_lines);
+ }
+ descriptionView.setMaxLines(sDefaultMaxLine);
+ }
descriptionView.setVisibility(View.VISIBLE);
descriptionView.setText(mDescription);
} else {
diff --git a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
index 9cc54ed2..297e69d9 100644
--- a/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
+++ b/src/com/android/tv/ui/sidepanel/CustomizeChannelListFragment.java
@@ -16,6 +16,8 @@
package com.android.tv.ui.sidepanel;
+import android.content.Context;
+import android.content.SharedPreferences;
import android.media.tv.TvContract.Channels;
import android.os.Bundle;
import android.support.v17.leanback.widget.VerticalGridView;
@@ -27,6 +29,7 @@ import android.widget.TextView;
import com.android.tv.MainActivity;
import com.android.tv.R;
+import com.android.tv.common.SharedPreferencesUtils;
import com.android.tv.data.Channel;
import com.android.tv.data.ChannelNumber;
import com.android.tv.ui.OnRepeatedKeyInterceptListener;
@@ -36,39 +39,38 @@ import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.List;
import java.util.Iterator;
+import java.util.List;
public class CustomizeChannelListFragment extends SideFragment {
private static final int GROUP_BY_SOURCE = 0;
private static final int GROUP_BY_HD_SD = 1;
private static final String TRACKER_LABEL = "customize channel list";
- private final List<Channel> mChannels = new ArrayList<>();
- private final long mInitialChannelId;
+ private static final String PREF_KEY_GROUP_SETTINGS = "pref_key_group_settigns";
+ private final List<Channel> mChannels = new ArrayList<>();
+ private long mInitialChannelId = Channel.INVALID_ID;
private long mLastFocusedChannelId = Channel.INVALID_ID;
- private int mGroupingType = GROUP_BY_SOURCE;
+ private static Integer sGroupingType;
private TvInputManagerHelper mInputManager;
private Channel.DefaultComparator mChannelComparator;
private boolean mGroupByFragmentRunning;
private final List<Item> mItems = new ArrayList<>();
- public CustomizeChannelListFragment() {
- this(Channel.INVALID_ID);
- }
-
- public CustomizeChannelListFragment(long initialChannelId) {
- mInitialChannelId = initialChannelId;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mInputManager = getMainActivity().getTvInputManagerHelper();
+ mInitialChannelId = getMainActivity().getCurrentChannelId();
mChannelComparator = new Channel.DefaultComparator(getActivity(), mInputManager);
+ if (sGroupingType == null) {
+ SharedPreferences sharedPreferences = getContext().getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE);
+ sGroupingType = sharedPreferences.getInt(PREF_KEY_GROUP_SETTINGS, GROUP_BY_SOURCE);
+ }
}
@Override
@@ -128,13 +130,16 @@ public class CustomizeChannelListFragment extends SideFragment {
@Override
public void onDestroyView() {
getChannelDataManager().applyUpdatedValuesToDb();
- if (!mGroupByFragmentRunning) {
- getMainActivity().endShrunkenTvView();
- }
super.onDestroyView();
}
@Override
+ public void onDestroy() {
+ super.onDestroy();
+ getMainActivity().endShrunkenTvView();
+ }
+
+ @Override
protected String getTitle() {
return getString(R.string.side_panel_title_edit_channels_for_an_input);
}
@@ -149,7 +154,7 @@ public class CustomizeChannelListFragment extends SideFragment {
mItems.clear();
mChannels.clear();
mChannels.addAll(getChannelDataManager().getChannelList());
- if (mGroupingType == GROUP_BY_SOURCE) {
+ if (sGroupingType == GROUP_BY_SOURCE) {
addItemForGroupBySource(mItems);
} else {
// GROUP_BY_HD_SD
@@ -321,6 +326,49 @@ public class CustomizeChannelListFragment extends SideFragment {
}
}
+ public static class GroupByFragment extends SideFragment {
+ @Override
+ protected String getTitle() {
+ return getString(R.string.side_panel_title_group_by);
+ }
+ @Override
+ public String getTrackerLabel() {
+ return GroupBySubMenu.TRACKER_LABEL;
+ }
+
+ @Override
+ protected List<Item> getItemList() {
+ List<Item> items = new ArrayList<>();
+ items.add(new RadioButtonItem(
+ getString(R.string.edit_channels_group_by_sources)) {
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ setGroupingType(GROUP_BY_SOURCE);
+ closeFragment();
+ }
+ });
+ items.add(new RadioButtonItem(
+ getString(R.string.edit_channels_group_by_hd_sd)) {
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ setGroupingType(GROUP_BY_HD_SD);
+ closeFragment();
+ }
+ });
+ ((RadioButtonItem) items.get(sGroupingType)).setChecked(true);
+ return items;
+ }
+
+ private void setGroupingType(int groupingType) {
+ sGroupingType = groupingType;
+ SharedPreferences sharedPreferences = getContext().getSharedPreferences(
+ SharedPreferencesUtils.SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE);
+ sharedPreferences.edit().putInt(PREF_KEY_GROUP_SETTINGS, groupingType).apply();
+ }
+ }
+
private class GroupBySubMenu extends SubMenuItem {
private static final String TRACKER_LABEL = "Group by";
public GroupBySubMenu(String description) {
@@ -330,41 +378,7 @@ public class CustomizeChannelListFragment extends SideFragment {
@Override
protected SideFragment getFragment() {
- return new SideFragment() {
- @Override
- protected String getTitle() {
- return getString(R.string.side_panel_title_group_by);
- }
- @Override
- public String getTrackerLabel() {
- return GroupBySubMenu.TRACKER_LABEL;
- }
-
- @Override
- protected List<Item> getItemList() {
- List<Item> items = new ArrayList<>();
- items.add(new RadioButtonItem(
- getString(R.string.edit_channels_group_by_sources)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- mGroupingType = GROUP_BY_SOURCE;
- closeFragment();
- }
- });
- items.add(new RadioButtonItem(
- getString(R.string.edit_channels_group_by_hd_sd)) {
- @Override
- protected void onSelected() {
- super.onSelected();
- mGroupingType = GROUP_BY_HD_SD;
- closeFragment();
- }
- });
- ((RadioButtonItem) items.get(mGroupingType)).setChecked(true);
- return items;
- }
- };
+ return new GroupByFragment();
}
@Override
diff --git a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
index 0d189cca..f633fa5a 100644
--- a/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
+++ b/src/com/android/tv/ui/sidepanel/DeveloperOptionFragment.java
@@ -18,8 +18,6 @@ package com.android.tv.ui.sidepanel;
import android.accounts.Account;
import android.app.Activity;
-import android.app.ApplicationErrorReport;
-import android.content.Intent;
import android.support.annotation.NonNull;
import android.util.Log;
import android.widget.Toast;
@@ -27,9 +25,11 @@ import android.widget.Toast;
import com.android.tv.R;
import com.android.tv.TvApplication;
import com.android.tv.common.BuildConfig;
+import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.experiments.Experiments;
import com.android.tv.tuner.TunerPreferences;
+import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -54,7 +54,15 @@ public class DeveloperOptionFragment extends SideFragment {
@Override
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
- if (BuildConfig.ENG) {
+ if (CommonFeatures.DVR.isEnabled(getContext())) {
+ items.add(new ActionItem(getString(R.string.dev_item_dvr_history)) {
+ @Override
+ protected void onSelected() {
+ getMainActivity().getOverlayManager().showDvrHistoryDialog();
+ }
+ });
+ }
+ if (Utils.isDeveloper()) {
items.add(new ActionItem(getString(R.string.dev_item_watch_history)) {
@Override
protected void onSelected() {
@@ -62,18 +70,6 @@ public class DeveloperOptionFragment extends SideFragment {
}
});
}
- items.add(new ActionItem(getString(R.string.dev_item_send_feedback)) {
- @Override
- protected void onSelected() {
- Intent intent = new Intent(Intent.ACTION_APP_ERROR);
- ApplicationErrorReport report = new ApplicationErrorReport();
- report.packageName = report.processName = getContext().getPackageName();
- report.time = System.currentTimeMillis();
- report.type = ApplicationErrorReport.TYPE_NONE;
- intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
- startActivityForResult(intent, 0);
- }
- });
items.add(new SwitchItem(getString(R.string.dev_item_store_ts_on),
getString(R.string.dev_item_store_ts_off),
getString(R.string.dev_item_store_ts_description)) {
@@ -89,13 +85,18 @@ public class DeveloperOptionFragment extends SideFragment {
TunerPreferences.setStoreTsStream(getContext(), isChecked());
}
});
+ if (Utils.isDeveloper()) {
+ items.add(
+ new ActionItem(getString(R.string.dev_item_show_performance_monitor_log)) {
+ @Override
+ protected void onSelected() {
+ TvApplication.getSingletons(getContext())
+ .getPerformanceMonitor()
+ .startPerformanceMonitorEventDebugActivity(getContext());
+ }
+ });
+ }
return items;
}
-
- /** True if there is the dev options menu */
- public static boolean shouldShow() {
- return Experiments.ENABLE_DEVELOPER_FEATURES.get() || BuildConfig.ENG;
- }
-
-}
+} \ No newline at end of file
diff --git a/src/com/android/tv/ui/sidepanel/Item.java b/src/com/android/tv/ui/sidepanel/Item.java
index 00f16427..4e47e75b 100644
--- a/src/com/android/tv/ui/sidepanel/Item.java
+++ b/src/com/android/tv/ui/sidepanel/Item.java
@@ -24,6 +24,7 @@ import android.view.ViewGroup;
public abstract class Item {
private View mItemView;
private boolean mEnabled = true;
+ private boolean mClickable = true;
public void setEnabled(boolean enabled) {
if (mEnabled != enabled) {
@@ -35,6 +36,16 @@ public abstract class Item {
}
/**
+ * Sets the item to be clickable or not.
+ */
+ public void setClickable(boolean clickable) {
+ mClickable = clickable;
+ if (mItemView != null) {
+ mItemView.setClickable(clickable);
+ }
+ }
+
+ /**
* Returns whether this item is enabled.
*/
public boolean isEnabled() {
@@ -64,6 +75,7 @@ public abstract class Item {
*/
protected void onUpdate() {
setEnabledInternal(mItemView, mEnabled);
+ mItemView.setClickable(mClickable);
}
protected abstract void onSelected();
diff --git a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java b/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java
deleted file mode 100644
index dec017a8..00000000
--- a/src/com/android/tv/ui/sidepanel/PipInputSelectorFragment.java
+++ /dev/null
@@ -1,170 +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.ui.sidepanel;
-
-import android.media.tv.TvInputInfo;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.tv.R;
-import com.android.tv.util.PipInputManager;
-import com.android.tv.util.PipInputManager.PipInput;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-public class PipInputSelectorFragment extends SideFragment {
- private static final String TAG = "PipInputSelector";
- private static final String TRACKER_LABEL = "PIP input source";
-
- private final List<Item> mInputItems = new ArrayList<>();
- private PipInputManager mPipInputManager;
- private PipInput mInitialPipInput;
- private boolean mSelected;
-
- private final PipInputManager.Listener mPipInputListener = new PipInputManager.Listener() {
- @Override
- public void onPipInputStateUpdated() {
- notifyDataSetChanged();
- }
-
- @Override
- public void onPipInputListUpdated() {
- refreshInputList();
- setItems(mInputItems);
- }
- };
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- mPipInputManager = getMainActivity().getPipInputManager();
- mPipInputManager.addListener(mPipInputListener);
- getMainActivity().startShrunkenTvView(false, false);
- return super.onCreateView(inflater, container, savedInstanceState);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- mInitialPipInput = mPipInputManager.getPipInput(getMainActivity().getPipChannel());
- if (mInitialPipInput == null) {
- Log.w(TAG, "PIP should be on");
- closeFragment();
- }
- int count = 0;
- for (Item item : mInputItems) {
- InputItem inputItem = (InputItem) item;
- if (Objects.equals(inputItem.mPipInput, mInitialPipInput)) {
- setSelectedPosition(count);
- break;
- }
- ++count;
- }
- }
-
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mPipInputManager.removeListener(mPipInputListener);
- if (!mSelected) {
- getMainActivity().tuneToChannelForPip(mInitialPipInput.getChannel());
- }
- getMainActivity().endShrunkenTvView();
- }
-
- @Override
- protected String getTitle() {
- return getString(R.string.side_panel_title_pip_input_source);
- }
-
- @Override
- public String getTrackerLabel() {
- return TRACKER_LABEL;
- }
-
- @Override
- protected List<Item> getItemList() {
- refreshInputList();
- return mInputItems;
- }
-
- private void refreshInputList() {
- mInputItems.clear();
- for (PipInput input : mPipInputManager.getPipInputList(false)) {
- mInputItems.add(new InputItem(input));
- }
- }
-
- private class InputItem extends RadioButtonItem {
- private final PipInput mPipInput;
-
- private InputItem(PipInput input) {
- super(input.getLongLabel());
- mPipInput = input;
- setEnabled(isAvailable());
- }
-
- @Override
- protected void onUpdate() {
- super.onUpdate();
- setEnabled(mPipInput.isAvailable());
- setChecked(mPipInput == mInitialPipInput);
- }
-
- @Override
- protected void onFocused() {
- super.onFocused();
- if (isEnabled()) {
- getMainActivity().tuneToChannelForPip(mPipInput.getChannel());
- }
- }
-
- @Override
- protected void onSelected() {
- super.onSelected();
- if (isEnabled()) {
- mSelected = true;
- closeFragment();
- }
- }
-
- private boolean isAvailable() {
- if (!mPipInput.isAvailable()) {
- return false;
- }
-
- // If this input shares the same parent with the current main input, you cannot select
- // it. (E.g. two HDMI CEC devices that are connected to HDMI port 1 through an A/V
- // receiver.)
- PipInput pipInput = mPipInputManager.getPipInput(getMainActivity().getCurrentChannel());
- if (pipInput == null) {
- return false;
- }
- TvInputInfo mainInputInfo = pipInput.getInputInfo();
- TvInputInfo pipInputInfo = mPipInput.getInputInfo();
- return mainInputInfo == null || pipInputInfo == null
- || !TextUtils.equals(mainInputInfo.getId(), pipInputInfo.getId())
- && !TextUtils.equals(mainInputInfo.getParentId(), pipInputInfo.getParentId());
- }
- }
-}
diff --git a/src/com/android/tv/ui/sidepanel/SettingsFragment.java b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
index e8033a22..6a5b510c 100644
--- a/src/com/android/tv/ui/sidepanel/SettingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SettingsFragment.java
@@ -16,18 +16,25 @@
package com.android.tv.ui.sidepanel;
+import static com.android.tv.Features.TUNER;
+
+import android.app.ApplicationErrorReport;
+import android.content.Intent;
+import android.media.tv.TvInputInfo;
import android.view.View;
import android.widget.Toast;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
+import com.android.tv.customization.TvCustomizationManager;
import com.android.tv.dialog.PinDialogFragment;
-import com.android.tv.dialog.WebDialogFragment;
-import com.android.tv.license.LicenseUtils;
-import com.android.tv.ui.sidepanel.parentalcontrols.ParentalControlsFragment;
+import com.android.tv.license.LicenseSideFragment;
+import com.android.tv.license.Licenses;
+import com.android.tv.tuner.TunerPreferences;
import com.android.tv.util.PermissionUtils;
import com.android.tv.util.SetupUtils;
+import com.android.tv.util.Utils;
import java.util.ArrayList;
import java.util.List;
@@ -38,33 +45,6 @@ import java.util.List;
public class SettingsFragment extends SideFragment {
private static final String TRACKER_LABEL = "settings";
- private final long mCurrentChannelId;
-
- public SettingsFragment(long currentChannelId) {
- mCurrentChannelId = currentChannelId;
- }
-
- /**
- * Opens a dialog showing open source licenses.
- */
- public static final class LicenseActionItem extends ActionItem {
- public final static String DIALOG_TAG = LicenseActionItem.class.getSimpleName();
- public static final String TRACKER_LABEL = "Open Source Licenses";
- private final MainActivity mMainActivity;
-
- public LicenseActionItem(MainActivity mainActivity) {
- super(mainActivity.getString(R.string.settings_menu_licenses));
- mMainActivity = mainActivity;
- }
-
- @Override
- protected void onSelected() {
- WebDialogFragment dialog = WebDialogFragment.newInstance(LicenseUtils.LICENSE_FILE,
- mMainActivity.getString(R.string.dialog_title_licenses), TRACKER_LABEL);
- mMainActivity.getOverlayManager().showDialogFragment(DIALOG_TAG, dialog, false);
- }
- }
-
@Override
protected String getTitle() {
return getResources().getString(R.string.side_panel_title_settings);
@@ -80,11 +60,11 @@ public class SettingsFragment extends SideFragment {
List<Item> items = new ArrayList<>();
final Item customizeChannelListItem = new SubMenuItem(
getString(R.string.settings_channel_source_item_customize_channels),
- getString(R.string.settings_channel_source_item_customize_channels_description), 0,
+ getString(R.string.settings_channel_source_item_customize_channels_description),
getMainActivity().getOverlayManager().getSideFragmentManager()) {
@Override
protected SideFragment getFragment() {
- return new CustomizeChannelListFragment(mCurrentChannelId);
+ return new CustomizeChannelListFragment();
}
@Override
@@ -122,25 +102,11 @@ public class SettingsFragment extends SideFragment {
: R.string.option_toggle_parental_controls_off)) {
@Override
protected void onSelected() {
- final MainActivity tvActivity = getMainActivity();
- final SideFragmentManager sideFragmentManager = tvActivity.getOverlayManager()
- .getSideFragmentManager();
- sideFragmentManager.hideSidePanel(true);
- PinDialogFragment fragment = new PinDialogFragment(
- PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN,
- new PinDialogFragment.ResultListener() {
- @Override
- public void done(boolean success) {
- if (success) {
- sideFragmentManager
- .show(new ParentalControlsFragment(), false);
- sideFragmentManager.showSidePanel(true);
- } else {
- sideFragmentManager.hideAll(false);
- }
- }
- });
- tvActivity.getOverlayManager()
+ getMainActivity().getOverlayManager()
+ .getSideFragmentManager().hideSidePanel(true);
+ PinDialogFragment fragment = PinDialogFragment
+ .create(PinDialogFragment.PIN_DIALOG_TYPE_ENTER_PIN);
+ getMainActivity().getOverlayManager()
.showDialogFragment(PinDialogFragment.DIALOG_TAG, fragment, true);
}
});
@@ -149,12 +115,73 @@ public class SettingsFragment extends SideFragment {
// But, we may be able to turn on channel lock feature regardless of the permission.
// It's TBD.
}
- if (LicenseUtils.hasLicenses(activity.getAssets())) {
- items.add(new LicenseActionItem(activity));
+ boolean showTrickplaySetting = false;
+ if (TUNER.isEnabled(getContext())) {
+ for (TvInputInfo inputInfo : TvApplication.getSingletons(getContext())
+ .getTvInputManagerHelper().getTvInputInfos(true, true)) {
+ if (Utils.isInternalTvInput(getContext(), inputInfo.getId())) {
+ showTrickplaySetting = true;
+ break;
+ }
+ }
+ if (showTrickplaySetting) {
+ showTrickplaySetting =
+ TvCustomizationManager.getTrickplayMode(getContext())
+ == TvCustomizationManager.TRICKPLAY_MODE_ENABLED;
+ }
+ }
+ if (showTrickplaySetting) {
+ items.add(
+ new SwitchItem(getString(R.string.settings_trickplay),
+ getString(R.string.settings_trickplay),
+ getString(R.string.settings_trickplay_description),
+ getResources().getInteger(R.integer.trickplay_description_max_lines)) {
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ boolean enabled = TunerPreferences.getTrickplaySetting(getContext())
+ != TunerPreferences.TRICKPLAY_SETTING_DISABLED;
+ setChecked(enabled);
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ @TunerPreferences.TrickplaySetting int setting =
+ isChecked() ? TunerPreferences.TRICKPLAY_SETTING_ENABLED
+ : TunerPreferences.TRICKPLAY_SETTING_DISABLED;
+ TunerPreferences.setTrickplaySetting(getContext(), setting);
+ }
+ });
+ }
+ items.add(new ActionItem(getString(R.string.settings_send_feedback)) {
+ @Override
+ protected void onSelected() {
+ Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+ ApplicationErrorReport report = new ApplicationErrorReport();
+ report.packageName = report.processName = getContext().getPackageName();
+ report.time = System.currentTimeMillis();
+ report.type = ApplicationErrorReport.TYPE_NONE;
+ intent.putExtra(Intent.EXTRA_BUG_REPORT, report);
+ startActivityForResult(intent, 0);
+ }
+ });
+ if (Licenses.hasLicenses(getContext())) {
+ items.add(
+ new SubMenuItem(
+ getString(R.string.settings_menu_licenses),
+ getMainActivity().getOverlayManager().getSideFragmentManager()) {
+ @Override
+ protected SideFragment getFragment() {
+ return new LicenseSideFragment();
+ }
+ });
}
// Show version.
- items.add(new SimpleItem(getString(R.string.settings_menu_version),
- ((TvApplication) activity.getApplicationContext()).getVersionName()));
+ SimpleActionItem version = new SimpleActionItem(getString(R.string.settings_menu_version),
+ ((TvApplication) activity.getApplicationContext()).getVersionName());
+ version.setClickable(false);
+ items.add(version);
return items;
}
diff --git a/src/com/android/tv/ui/sidepanel/SideFragment.java b/src/com/android/tv/ui/sidepanel/SideFragment.java
index 8df56cd2..6bd921a2 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragment.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragment.java
@@ -26,32 +26,36 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.FrameLayout;
import android.widget.TextView;
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.TvApplication;
-import com.android.tv.analytics.DurationTimer;
+import com.android.tv.util.DurationTimer;
import com.android.tv.analytics.HasTrackerLabel;
import com.android.tv.analytics.Tracker;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.util.SystemProperties;
+import com.android.tv.util.ViewCache;
import java.util.List;
-public abstract class SideFragment extends Fragment implements HasTrackerLabel {
+public abstract class SideFragment<T extends Item> extends Fragment implements HasTrackerLabel {
public static final int INVALID_POSITION = -1;
- private static final int RECYCLED_VIEW_POOL_SIZE = 7;
- private static final int[] PRELOADED_VIEW_IDS = {
+ private static final int PRELOAD_VIEW_SIZE = 7;
+ private static final int[] PRELOAD_VIEW_IDS = {
R.layout.option_item_radio_button,
R.layout.option_item_channel_lock,
R.layout.option_item_check_box,
- R.layout.option_item_channel_check
+ R.layout.option_item_channel_check,
+ R.layout.option_item_action
};
- private static RecyclerView.RecycledViewPool sRecycledViewPool;
+ private static RecyclerView.RecycledViewPool sRecycledViewPool =
+ new RecyclerView.RecycledViewPool();
private VerticalGridView mListView;
private ItemAdapter mAdapter;
@@ -89,14 +93,8 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- if (sRecycledViewPool == null) {
- // sRecycledViewPool should be initialized by calling preloadRecycledViews()
- // before the entering animation of this fragment starts,
- // because it takes long time and if it is called after the animation starts (e.g. here)
- // it can affect the animation.
- throw new IllegalStateException("The RecyclerView pool has not been initialized.");
- }
- View view = inflater.inflate(getFragmentLayoutResourceId(), container, false);
+ View view = ViewCache.getInstance().getOrCreateView(
+ inflater, getFragmentLayoutResourceId(), container);
TextView textView = (TextView) view.findViewById(R.id.side_panel_title);
textView.setText(getTitle());
@@ -158,7 +156,7 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
return mListView.getSelectedPosition();
}
- public void setItems(List<Item> items) {
+ public void setItems(List<T> items) {
mAdapter.reset(items);
}
@@ -229,56 +227,50 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
protected abstract String getTitle();
@Override
public abstract String getTrackerLabel();
- protected abstract List<Item> getItemList();
+ protected abstract List<T> getItemList();
public interface SideFragmentListener {
void onSideFragmentViewDestroyed();
}
/**
- * Preloads the view holders.
+ * Preloads the item views.
*/
- public static void preloadRecycledViews(Context context) {
- if (sRecycledViewPool != null) {
- return;
- }
- sRecycledViewPool = new RecyclerView.RecycledViewPool();
- LayoutInflater inflater =
- (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- for (int id : PRELOADED_VIEW_IDS) {
- sRecycledViewPool.setMaxRecycledViews(id, RECYCLED_VIEW_POOL_SIZE);
- for (int j = 0; j < RECYCLED_VIEW_POOL_SIZE; ++j) {
- ItemAdapter.ViewHolder viewHolder = new ItemAdapter.ViewHolder(
- inflater.inflate(id, null, false));
- sRecycledViewPool.putRecycledView(viewHolder);
- }
+ public static void preloadItemViews(Context context) {
+ ViewCache.getInstance().putView(
+ context, R.layout.option_fragment, new FrameLayout(context), 1);
+ VerticalGridView fakeParent = new VerticalGridView(context);
+ for (int id : PRELOAD_VIEW_IDS) {
+ sRecycledViewPool.setMaxRecycledViews(id, PRELOAD_VIEW_SIZE);
+ ViewCache.getInstance().putView(context, id, fakeParent, PRELOAD_VIEW_SIZE);
}
}
/**
- * Releases the pre-loaded view holders.
+ * Releases the recycled view pool.
*/
- public static void releasePreloadedRecycledViews() {
- sRecycledViewPool = null;
+ public static void releaseRecycledViewPool() {
+ sRecycledViewPool.clear();
}
- private static class ItemAdapter extends RecyclerView.Adapter<ItemAdapter.ViewHolder> {
+ private static class ItemAdapter<T extends Item> extends RecyclerView.Adapter<ViewHolder> {
private final LayoutInflater mLayoutInflater;
- private List<Item> mItems;
+ private List<T> mItems;
- private ItemAdapter(LayoutInflater layoutInflater, List<Item> items) {
+ private ItemAdapter(LayoutInflater layoutInflater, List<T> items) {
mLayoutInflater = layoutInflater;
mItems = items;
}
- private void reset(List<Item> items) {
+ private void reset(List<T> items) {
mItems = items;
notifyDataSetChanged();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- return new ViewHolder(mLayoutInflater.inflate(viewType, parent, false));
+ View view = ViewCache.getInstance().getOrCreateView(mLayoutInflater, viewType, parent);
+ return new ViewHolder(view);
}
@Override
@@ -301,11 +293,11 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
return mItems == null ? 0 : mItems.size();
}
- private Item getItem(int position) {
+ private T getItem(int position) {
return mItems.get(position);
}
- private void clearRadioGroup(Item item) {
+ private void clearRadioGroup(T item) {
int position = mItems.indexOf(item);
for (int i = position - 1; i >= 0; --i) {
if ((item = mItems.get(i)) instanceof RadioButtonItem) {
@@ -322,55 +314,57 @@ public abstract class SideFragment extends Fragment implements HasTrackerLabel {
}
}
}
+ }
- private static class ViewHolder extends RecyclerView.ViewHolder
- implements View.OnClickListener, View.OnFocusChangeListener {
- private ItemAdapter mAdapter;
- public Item mItem;
+ private static class ViewHolder extends RecyclerView.ViewHolder
+ implements View.OnClickListener, View.OnFocusChangeListener {
+ private ItemAdapter mAdapter;
+ public Item mItem;
- private ViewHolder(View view) {
- super(view);
- itemView.setOnClickListener(this);
- itemView.setOnFocusChangeListener(this);
- }
+ private ViewHolder(View view) {
+ super(view);
+ itemView.setOnClickListener(this);
+ itemView.setOnFocusChangeListener(this);
+ }
- public void onBind(ItemAdapter adapter, Item item) {
- mAdapter = adapter;
- mItem = item;
- mItem.onBind(itemView);
- mItem.onUpdate();
- }
+ public void onBind(ItemAdapter adapter, Item item) {
+ mAdapter = adapter;
+ mItem = item;
+ mItem.onBind(itemView);
+ mItem.onUpdate();
+ }
- public void onUnbind() {
- mItem.onUnbind();
- mItem = null;
- mAdapter = null;
- }
+ public void onUnbind() {
+ mItem.onUnbind();
+ mItem = null;
+ mAdapter = null;
+ }
- @Override
- public void onClick(View view) {
- if (mItem instanceof RadioButtonItem) {
- mAdapter.clearRadioGroup(mItem);
- }
- if (view.getBackground() instanceof RippleDrawable) {
- view.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (mItem != null) {
- mItem.onSelected();
+ @Override
+ public void onClick(View view) {
+ if (mItem instanceof RadioButtonItem) {
+ mAdapter.clearRadioGroup(mItem);
+ }
+ if (view.getBackground() instanceof RippleDrawable) {
+ view.postDelayed(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (mItem != null) {
+ mItem.onSelected();
+ }
}
- }
- }, view.getResources().getInteger(R.integer.side_panel_ripple_anim_duration));
- } else {
- mItem.onSelected();
- }
+ },
+ view.getResources().getInteger(R.integer.side_panel_ripple_anim_duration));
+ } else {
+ mItem.onSelected();
}
+ }
- @Override
- public void onFocusChange(View view, boolean focusGained) {
- if (focusGained) {
- mItem.onFocused();
- }
+ @Override
+ public void onFocusChange(View view, boolean focusGained) {
+ if (focusGained) {
+ mItem.onFocused();
}
}
}
diff --git a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
index 553cd9d7..d02d3fb7 100644
--- a/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
+++ b/src/com/android/tv/ui/sidepanel/SideFragmentManager.java
@@ -24,6 +24,7 @@ import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.os.Handler;
import android.view.View;
+import android.view.ViewTreeObserver;
import com.android.tv.R;
@@ -34,6 +35,7 @@ public class SideFragmentManager {
private final FragmentManager mFragmentManager;
private final Runnable mPreShowRunnable;
private final Runnable mPostHideRunnable;
+ private ViewTreeObserver.OnGlobalLayoutListener mShowOnGlobalLayoutListener;
// To get the count reliably while using popBackStack(),
// instead of using getBackStackEntryCount() with popBackStackImmediate().
@@ -99,17 +101,10 @@ public class SideFragmentManager {
* Shows the given {@link SideFragment}.
*/
public void show(SideFragment sideFragment, boolean showEnterAnimation) {
- SideFragment.preloadRecycledViews(mActivity);
if (isHiding()) {
mHideAnimator.end();
}
boolean isFirst = (mFragmentCount == 0);
- if (isFirst) {
- if (mPreShowRunnable != null) {
- mPreShowRunnable.run();
- }
- }
-
FragmentTransaction ft = mFragmentManager.beginTransaction();
if (!isFirst) {
ft.setCustomAnimations(
@@ -123,8 +118,22 @@ public class SideFragmentManager {
mFragmentCount++;
if (isFirst) {
+ // We should wait for fragment transition and intital layouting finished to start the
+ // slide-in animation to prevent jankiness resulted by performing transition and
+ // layouting at the same time with animation.
mPanel.setVisibility(View.VISIBLE);
- mShowAnimator.start();
+ mShowOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mShowOnGlobalLayoutListener = null;
+ if (mPreShowRunnable != null) {
+ mPreShowRunnable.run();
+ }
+ mShowAnimator.start();
+ }
+ };
+ mPanel.getViewTreeObserver().addOnGlobalLayoutListener(mShowOnGlobalLayoutListener);
}
scheduleHideAll();
}
@@ -142,6 +151,18 @@ public class SideFragmentManager {
}
public void hideAll(boolean withAnimation) {
+ if (mShowAnimator.isStarted()) {
+ mShowAnimator.end();
+ }
+ if (mShowOnGlobalLayoutListener != null) {
+ // The show operation maybe requested but the show animator is not started yet, in this
+ // case, we show still run mPreShowRunnable.
+ mPanel.getViewTreeObserver().removeOnGlobalLayoutListener(mShowOnGlobalLayoutListener);
+ mShowOnGlobalLayoutListener = null;
+ if (mPreShowRunnable != null) {
+ mPreShowRunnable.run();
+ }
+ }
if (withAnimation) {
if (!isHiding()) {
mHideAnimator.start();
@@ -178,7 +199,6 @@ public class SideFragmentManager {
* @param withAnimation specifies if animation should be shown.
*/
public void showSidePanel(boolean withAnimation) {
- SideFragment.preloadRecycledViews(mActivity);
if (mFragmentCount == 0) {
return;
}
diff --git a/src/com/android/tv/ui/sidepanel/SimpleItem.java b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java
index 52a5f13f..42553b66 100644
--- a/src/com/android/tv/ui/sidepanel/SimpleItem.java
+++ b/src/com/android/tv/ui/sidepanel/SimpleActionItem.java
@@ -19,12 +19,12 @@ package com.android.tv.ui.sidepanel;
/**
* A simple item which shows title and description.
*/
-public class SimpleItem extends ActionItem {
- public SimpleItem(String title) {
+public class SimpleActionItem extends ActionItem {
+ public SimpleActionItem(String title) {
super(title);
}
- public SimpleItem(String title, String description) {
+ public SimpleActionItem(String title, String description) {
super(title, description);
}
diff --git a/src/com/android/tv/ui/sidepanel/SubMenuItem.java b/src/com/android/tv/ui/sidepanel/SubMenuItem.java
index aa349dbd..4b0e8e2c 100644
--- a/src/com/android/tv/ui/sidepanel/SubMenuItem.java
+++ b/src/com/android/tv/ui/sidepanel/SubMenuItem.java
@@ -21,20 +21,11 @@ public abstract class SubMenuItem extends ActionItem {
private final SideFragmentManager mSideFragmentManager;
public SubMenuItem(String title, SideFragmentManager fragmentManager) {
- this(title, null, 0, fragmentManager);
+ this(title, null, fragmentManager);
}
public SubMenuItem(String title, String description, SideFragmentManager fragmentManager) {
- this(title, description, 0, fragmentManager);
- }
-
- public SubMenuItem(String title, int iconId, SideFragmentManager fragmentManager) {
- this(title, null, iconId, fragmentManager);
- }
-
- public SubMenuItem(String title, String description, int iconId,
- SideFragmentManager fragmentManager) {
- super(title, description, iconId);
+ super(title, description);
mSideFragmentManager = fragmentManager;
}
diff --git a/src/com/android/tv/ui/sidepanel/SwitchItem.java b/src/com/android/tv/ui/sidepanel/SwitchItem.java
index ef9966a5..06591b62 100644
--- a/src/com/android/tv/ui/sidepanel/SwitchItem.java
+++ b/src/com/android/tv/ui/sidepanel/SwitchItem.java
@@ -31,6 +31,11 @@ public class SwitchItem extends CompoundButtonItem {
super(checkedTitle, uncheckedTitle, description);
}
+ public SwitchItem(String checkedTitle, String uncheckedTitle, String description,
+ int maxLines) {
+ super(checkedTitle, uncheckedTitle, description, maxLines);
+ }
+
@Override
protected int getResourceId() {
return R.layout.option_item_switch;
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
index da712924..9a4879fc 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/ParentalControlsFragment.java
@@ -130,17 +130,8 @@ public class ParentalControlsFragment extends SideFragment {
protected void onSelected() {
final MainActivity tvActivity = getMainActivity();
tvActivity.getOverlayManager().getSideFragmentManager().hideSidePanel(true);
-
- PinDialogFragment fragment =
- new PinDialogFragment(
- PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN,
- new PinDialogFragment.ResultListener() {
- @Override
- public void done(boolean success) {
- tvActivity.getOverlayManager().getSideFragmentManager()
- .showSidePanel(true);
- }
- });
+ PinDialogFragment fragment = PinDialogFragment.create(
+ PinDialogFragment.PIN_DIALOG_TYPE_NEW_PIN);
tvActivity.getOverlayManager().showDialogFragment(PinDialogFragment.DIALOG_TAG,
fragment, true);
}
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
index 6bc47939..7c8cecbe 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/RatingsFragment.java
@@ -17,16 +17,17 @@
package com.android.tv.ui.sidepanel.parentalcontrols;
import android.graphics.drawable.Drawable;
+import android.media.tv.TvContentRating;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.SparseIntArray;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
-
import com.android.tv.MainActivity;
import com.android.tv.R;
import com.android.tv.dialog.WebDialogFragment;
+import com.android.tv.experiments.Experiments;
import com.android.tv.license.LicenseUtils;
import com.android.tv.parental.ContentRatingSystem;
import com.android.tv.parental.ContentRatingSystem.Rating;
@@ -38,7 +39,6 @@ import com.android.tv.ui.sidepanel.RadioButtonItem;
import com.android.tv.ui.sidepanel.SideFragment;
import com.android.tv.util.TvSettings;
import com.android.tv.util.TvSettings.ContentRatingLevel;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -77,6 +77,7 @@ public class RatingsFragment extends SideFragment {
private final List<RatingLevelItem> mRatingLevelItems = new ArrayList<>();
// A map from the rating system ID string to RatingItem objects.
private final Map<String, List<RatingItem>> mContentRatingSystemItemMap = new ArrayMap<>();
+ private CheckBoxItem mBlockUnratedItem;
private ParentalControlSettings mParentalControlSettings;
public static String getDescription(MainActivity tvActivity) {
@@ -102,6 +103,12 @@ public class RatingsFragment extends SideFragment {
protected List<Item> getItemList() {
List<Item> items = new ArrayList<>();
+ if (mBlockUnratedItem != null
+ && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ items.add(mBlockUnratedItem);
+ items.add(new DividerItem());
+ }
+
mRatingLevelItems.clear();
for (int i = 0; i < sLevelResourceIdMap.size(); ++i) {
mRatingLevelItems.add(new RatingLevelItem(sLevelResourceIdMap.keyAt(i)));
@@ -152,6 +159,28 @@ public class RatingsFragment extends SideFragment {
super.onCreate(savedInstanceState);
mParentalControlSettings = getMainActivity().getParentalControlSettings();
mParentalControlSettings.loadRatings();
+ if (Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ mBlockUnratedItem =
+ new CheckBoxItem(
+ getResources().getString(R.string.option_block_unrated_programs)) {
+
+ @Override
+ protected void onUpdate() {
+ super.onUpdate();
+ setChecked(
+ mParentalControlSettings.isRatingBlocked(
+ new TvContentRating[] {TvContentRating.UNRATED}));
+ }
+
+ @Override
+ protected void onSelected() {
+ super.onSelected();
+ if (mParentalControlSettings.setUnratedBlocked(isChecked())) {
+ updateRatingLevels();
+ }
+ }
+ };
+ }
}
@Override
@@ -202,6 +231,13 @@ public class RatingsFragment extends SideFragment {
super.onSelected();
mParentalControlSettings.setContentRatingLevel(
getMainActivity().getContentRatingsManager(), mRatingLevel);
+ if (mBlockUnratedItem != null
+ && Boolean.TRUE.equals(Experiments.ENABLE_UNRATED_CONTENT_SETTINGS.get())) {
+ // set checked if UNRATED is blocked, and set unchecked otherwise.
+ mBlockUnratedItem.setChecked(
+ mParentalControlSettings.isRatingBlocked(
+ new TvContentRating[] {TvContentRating.UNRATED}));
+ }
notifyItemsChanged(mRatingLevelItems.size());
}
}
@@ -302,7 +338,7 @@ public class RatingsFragment extends SideFragment {
@Override
protected void onSelected() {
getMainActivity().getOverlayManager().getSideFragmentManager()
- .show(new SubRatingsFragment(mContentRatingSystem, mRating));
+ .show(SubRatingsFragment.create(mContentRatingSystem, mRating.getName()));
}
@Override
diff --git a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
index f6612fdb..4634b74c 100644
--- a/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
+++ b/src/com/android/tv/ui/sidepanel/parentalcontrols/SubRatingsFragment.java
@@ -17,6 +17,7 @@
package com.android.tv.ui.sidepanel.parentalcontrols;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
@@ -36,13 +37,34 @@ import java.util.List;
public class SubRatingsFragment extends SideFragment {
private static final String TRACKER_LABEL = "Sub ratings";
- private final ContentRatingSystem mContentRatingSystem;
- private final Rating mRating;
+ private static final String ARGS_CONTENT_RATING_SYSTEM_ID = "args_content_rating_system_id";
+ private static final String ARGS_RATING_NAME = "args_rating_name";
+
+ private ContentRatingSystem mContentRatingSystem;
+ private Rating mRating;
private final List<SubRatingItem> mSubRatingItems = new ArrayList<>();
- public SubRatingsFragment(ContentRatingSystem contentRatingSystem, Rating rating) {
- mContentRatingSystem = contentRatingSystem;
- mRating = rating;
+ public static SubRatingsFragment create(ContentRatingSystem contentRatingSystem,
+ String ratingName) {
+ SubRatingsFragment fragment = new SubRatingsFragment();
+ Bundle args = new Bundle();
+ args.putString(ARGS_CONTENT_RATING_SYSTEM_ID, contentRatingSystem.getId());
+ args.putString(ARGS_RATING_NAME, ratingName);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContentRatingSystem = getMainActivity().getContentRatingsManager()
+ .getContentRatingSystem(getArguments().getString(ARGS_CONTENT_RATING_SYSTEM_ID));
+ if (mContentRatingSystem != null) {
+ mRating = mContentRatingSystem.getRating(getArguments().getString(ARGS_RATING_NAME));
+ }
+ if (mRating == null) {
+ closeFragment();
+ }
}
@Override