aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/ui/TunableTvView.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/ui/TunableTvView.java')
-rw-r--r--src/com/android/tv/ui/TunableTvView.java644
1 files changed, 366 insertions, 278 deletions
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);
}
}
}