diff options
Diffstat (limited to 'src/com/android/tv/ui')
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 |