Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tv.ui;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ArgbEvaluator;
     22 import android.animation.ObjectAnimator;
     23 import android.animation.TimeInterpolator;
     24 import android.animation.TypeEvaluator;
     25 import android.animation.ValueAnimator;
     26 import android.animation.ValueAnimator.AnimatorUpdateListener;
     27 import android.app.Activity;
     28 import android.content.Context;
     29 import android.content.SharedPreferences;
     30 import android.content.res.Resources;
     31 import android.graphics.Point;
     32 import android.hardware.display.DisplayManager;
     33 import android.os.Build;
     34 import android.os.Handler;
     35 import android.os.Message;
     36 import android.preference.PreferenceManager;
     37 import android.util.Log;
     38 import android.util.Property;
     39 import android.view.Display;
     40 import android.view.ViewGroup;
     41 import android.view.ViewGroup.LayoutParams;
     42 import android.view.ViewGroup.MarginLayoutParams;
     43 import android.view.animation.AnimationUtils;
     44 import android.widget.FrameLayout;
     45 import com.android.tv.R;
     46 import com.android.tv.TvFeatures;
     47 import com.android.tv.TvOptionsManager;
     48 import com.android.tv.data.DisplayMode;
     49 import com.android.tv.util.TvSettings;
     50 
     51 /**
     52  * The TvViewUiManager is responsible for handling UI layouting and animation of main TvView. It
     53  * also control the settings regarding TvView UI such as display mode.
     54  */
     55 public class TvViewUiManager {
     56     private static final String TAG = "TvViewManager";
     57     private static final boolean DEBUG = false;
     58 
     59     private static final float DISPLAY_MODE_EPSILON = 0.001f;
     60     private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f;
     61 
     62     private static final int MSG_SET_LAYOUT_PARAMS = 1000;
     63 
     64     private final Context mContext;
     65     private final Resources mResources;
     66     private final FrameLayout mContentView;
     67     private final TunableTvView mTvView;
     68     private final TvOptionsManager mTvOptionsManager;
     69     private final int mTvViewShrunkenStartMargin;
     70     private final int mTvViewShrunkenEndMargin;
     71     private int mWindowWidth;
     72     private int mWindowHeight;
     73     private final SharedPreferences mSharedPreferences;
     74     private final TimeInterpolator mLinearOutSlowIn;
     75     private final TimeInterpolator mFastOutLinearIn;
     76     private final Handler mHandler =
     77             new Handler() {
     78                 @Override
     79                 public void handleMessage(Message msg) {
     80                     switch (msg.what) {
     81                         case MSG_SET_LAYOUT_PARAMS:
     82                             FrameLayout.LayoutParams layoutParams =
     83                                     (FrameLayout.LayoutParams) msg.obj;
     84                             if (DEBUG) {
     85                                 Log.d(
     86                                         TAG,
     87                                         "setFixedSize: w="
     88                                                 + layoutParams.width
     89                                                 + " h="
     90                                                 + layoutParams.height);
     91                             }
     92                             mTvView.setTvViewLayoutParams(layoutParams);
     93                             mTvView.setLayoutParams(mTvViewFrame);
     94                             // Smooth PIP size change, we don't change surface size when
     95                             // isInPictureInPictureMode is true.
     96                             if (!TvFeatures.PICTURE_IN_PICTURE.isEnabled(mContext)
     97                                     || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
     98                                             && !((Activity) mContext).isInPictureInPictureMode())) {
     99                                 mTvView.setFixedSurfaceSize(
    100                                         layoutParams.width, layoutParams.height);
    101                             }
    102                             break;
    103                     }
    104                 }
    105             };
    106     private int mDisplayMode;
    107     // Used to restore the previous state from ShrunkenTvView state.
    108     private int mTvViewStartMarginBeforeShrunken;
    109     private int mTvViewEndMarginBeforeShrunken;
    110     private int mDisplayModeBeforeShrunken;
    111     private boolean mIsUnderShrunkenTvView;
    112     private int mTvViewStartMargin;
    113     private int mTvViewEndMargin;
    114     private ObjectAnimator mTvViewAnimator;
    115     private FrameLayout.LayoutParams mTvViewLayoutParams;
    116     // TV view's position when the display mode is FULL. It is used to compute PIP location relative
    117     // to TV view's position.
    118     private FrameLayout.LayoutParams mTvViewFrame;
    119     private FrameLayout.LayoutParams mLastAnimatedTvViewFrame;
    120     private FrameLayout.LayoutParams mOldTvViewFrame;
    121     private ObjectAnimator mBackgroundAnimator;
    122     private int mBackgroundColor;
    123     private int mAppliedDisplayedMode = DisplayMode.MODE_NOT_DEFINED;
    124     private int mAppliedTvViewStartMargin;
    125     private int mAppliedTvViewEndMargin;
    126     private float mAppliedVideoDisplayAspectRatio;
    127 
    128     public TvViewUiManager(
    129             Context context,
    130             TunableTvView tvView,
    131             FrameLayout contentView,
    132             TvOptionsManager tvOptionManager) {
    133         mContext = context;
    134         mResources = mContext.getResources();
    135         mTvView = tvView;
    136         mContentView = contentView;
    137         mTvOptionsManager = tvOptionManager;
    138 
    139         DisplayManager displayManager =
    140                 (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
    141         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
    142         Point size = new Point();
    143         display.getSize(size);
    144         mWindowWidth = size.x;
    145         mWindowHeight = size.y;
    146 
    147         // Have an assumption that TvView Shrinking happens only in full screen.
    148         mTvViewShrunkenStartMargin =
    149                 mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start);
    150         mTvViewShrunkenEndMargin =
    151                 mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end)
    152                         + mResources.getDimensionPixelSize(R.dimen.side_panel_width);
    153         mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0);
    154 
    155         mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    156 
    157         mLinearOutSlowIn =
    158                 AnimationUtils.loadInterpolator(
    159                         mContext, android.R.interpolator.linear_out_slow_in);
    160         mFastOutLinearIn =
    161                 AnimationUtils.loadInterpolator(
    162                         mContext, android.R.interpolator.fast_out_linear_in);
    163     }
    164 
    165     public void onConfigurationChanged(final int windowWidth, final int windowHeight) {
    166         if (windowWidth > 0 && windowHeight > 0) {
    167             if (mWindowWidth != windowWidth || mWindowHeight != windowHeight) {
    168                 mWindowWidth = windowWidth;
    169                 mWindowHeight = windowHeight;
    170                 applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, true);
    171             }
    172         }
    173     }
    174 
    175     /**
    176      * Initializes animator in advance of using the animator to improve animation performance. For
    177      * fast first tune, it is not expected to be called in Activity.onCreate, but called a few
    178      * seconds later after onCreate.
    179      */
    180     public void initAnimatorIfNeeded() {
    181         initTvAnimatorIfNeeded();
    182         initBackgroundAnimatorIfNeeded();
    183     }
    184 
    185     /**
    186      * It is called when shrunken TvView is desired, such as EditChannelFragment and
    187      * ChannelsLockedFragment.
    188      */
    189     public void startShrunkenTvView() {
    190         mIsUnderShrunkenTvView = true;
    191         mTvView.setIsUnderShrunken(true);
    192 
    193         mTvViewStartMarginBeforeShrunken = mTvViewStartMargin;
    194         mTvViewEndMarginBeforeShrunken = mTvViewEndMargin;
    195         setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
    196         mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true);
    197     }
    198 
    199     /**
    200      * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
    201      * ChannelsLockedFragment.
    202      */
    203     public void endShrunkenTvView() {
    204         mIsUnderShrunkenTvView = false;
    205         mTvView.setIsUnderShrunken(false);
    206         setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken);
    207         setDisplayMode(mDisplayModeBeforeShrunken, false, true);
    208     }
    209 
    210     /** Returns true, if TvView is shrunken. */
    211     public boolean isUnderShrunkenTvView() {
    212         return mIsUnderShrunkenTvView;
    213     }
    214 
    215     /**
    216      * Returns true, if {@code displayMode} is available now. If screen ratio is matched to video
    217      * ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available.
    218      */
    219     public boolean isDisplayModeAvailable(int displayMode) {
    220         if (displayMode == DisplayMode.MODE_NORMAL) {
    221             return true;
    222         }
    223 
    224         int viewWidth = mContentView.getWidth();
    225         int viewHeight = mContentView.getHeight();
    226 
    227         float videoDisplayAspectRatio = mTvView.getVideoDisplayAspectRatio();
    228         if (viewWidth <= 0 || viewHeight <= 0 || videoDisplayAspectRatio <= 0f) {
    229             Log.w(TAG, "Video size is currently unavailable");
    230             if (DEBUG) {
    231                 Log.d(
    232                         TAG,
    233                         "isDisplayModeAvailable: "
    234                                 + "viewWidth="
    235                                 + viewWidth
    236                                 + ", viewHeight="
    237                                 + viewHeight
    238                                 + ", videoDisplayAspectRatio="
    239                                 + videoDisplayAspectRatio);
    240             }
    241             return false;
    242         }
    243 
    244         float viewRatio = viewWidth / (float) viewHeight;
    245         return Math.abs(viewRatio - videoDisplayAspectRatio) >= DISPLAY_MODE_EPSILON;
    246     }
    247 
    248     /** Returns a constant defined in DisplayMode. */
    249     public int getDisplayMode() {
    250         if (isDisplayModeAvailable(mDisplayMode)) {
    251             return mDisplayMode;
    252         }
    253         return DisplayMode.MODE_NORMAL;
    254     }
    255 
    256     /**
    257      * Sets the display mode to the given value.
    258      *
    259      * @return the previous display mode.
    260      */
    261     public int setDisplayMode(int displayMode, boolean storeInPreference, boolean animate) {
    262         int prev = mDisplayMode;
    263         mDisplayMode = displayMode;
    264         if (storeInPreference) {
    265             mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply();
    266         }
    267         applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), animate, false);
    268         return prev;
    269     }
    270 
    271     /** Restores the display mode to the display mode stored in preference. */
    272     public void restoreDisplayMode(boolean animate) {
    273         int displayMode =
    274                 mSharedPreferences.getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL);
    275         setDisplayMode(displayMode, false, animate);
    276     }
    277 
    278     /** Updates TvView's aspect ratio. It should be called when video resolution is changed. */
    279     public void updateTvAspectRatio() {
    280         applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false);
    281         if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) {
    282             mTvView.fadeIn(
    283                     mResources.getInteger(R.integer.tvview_fade_in_duration),
    284                     mFastOutLinearIn,
    285                     null);
    286         }
    287     }
    288 
    289     /** Fades in TvView. */
    290     public void fadeInTvView() {
    291         if (mTvView.isFadedOut()) {
    292             mTvView.fadeIn(
    293                     mResources.getInteger(R.integer.tvview_fade_in_duration),
    294                     mFastOutLinearIn,
    295                     null);
    296         }
    297     }
    298 
    299     /** Fades out TvView. */
    300     public void fadeOutTvView(Runnable postAction) {
    301         if (!mTvView.isFadedOut()) {
    302             mTvView.fadeOut(
    303                     mResources.getInteger(R.integer.tvview_fade_out_duration),
    304                     mLinearOutSlowIn,
    305                     postAction);
    306         }
    307     }
    308 
    309     /** This margins will be applied when applyDisplayMode is called. */
    310     private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
    311         mTvViewStartMargin = tvViewStartMargin;
    312         mTvViewEndMargin = tvViewEndMargin;
    313     }
    314 
    315     private boolean isTvViewFullScreen() {
    316         return mTvViewStartMargin == 0 && mTvViewEndMargin == 0;
    317     }
    318 
    319     private void setBackgroundColor(
    320             int color, FrameLayout.LayoutParams targetLayoutParams, boolean animate) {
    321         if (animate) {
    322             initBackgroundAnimatorIfNeeded();
    323             if (mBackgroundAnimator.isStarted()) {
    324                 // Cancel the current animation and start new one.
    325                 mBackgroundAnimator.cancel();
    326             }
    327 
    328             int decorViewWidth = mContentView.getWidth();
    329             int decorViewHeight = mContentView.getHeight();
    330             boolean hasPillarBox =
    331                     mTvView.getWidth() != decorViewWidth || mTvView.getHeight() != decorViewHeight;
    332             boolean willHavePillarBox =
    333                     ((targetLayoutParams.width != LayoutParams.MATCH_PARENT)
    334                                     && targetLayoutParams.width != decorViewWidth)
    335                             || ((targetLayoutParams.height != LayoutParams.MATCH_PARENT)
    336                                     && targetLayoutParams.height != decorViewHeight);
    337 
    338             if (!isTvViewFullScreen() && !hasPillarBox) {
    339                 // If there is no pillar box, no animation is needed.
    340                 mContentView.setBackgroundColor(color);
    341             } else if (!isTvViewFullScreen() || willHavePillarBox) {
    342                 mBackgroundAnimator.setIntValues(mBackgroundColor, color);
    343                 mBackgroundAnimator.setEvaluator(new ArgbEvaluator());
    344                 mBackgroundAnimator.setInterpolator(mFastOutLinearIn);
    345                 mBackgroundAnimator.start();
    346             }
    347             // In the 'else' case (TV activity is getting out of the shrunken tv view mode and will
    348             // have a pillar box), we keep the background color and don't show the animation.
    349         } else {
    350             mContentView.setBackgroundColor(color);
    351         }
    352         mBackgroundColor = color;
    353     }
    354 
    355     private void setTvViewPosition(
    356             final FrameLayout.LayoutParams layoutParams,
    357             FrameLayout.LayoutParams tvViewFrame,
    358             boolean animate) {
    359         if (DEBUG) {
    360             Log.d(
    361                     TAG,
    362                     "setTvViewPosition: w="
    363                             + layoutParams.width
    364                             + " h="
    365                             + layoutParams.height
    366                             + " s="
    367                             + layoutParams.getMarginStart()
    368                             + " t="
    369                             + layoutParams.topMargin
    370                             + " e="
    371                             + layoutParams.getMarginEnd()
    372                             + " b="
    373                             + layoutParams.bottomMargin
    374                             + " animate="
    375                             + animate);
    376         }
    377         FrameLayout.LayoutParams oldTvViewFrame = mTvViewFrame;
    378         mTvViewLayoutParams = layoutParams;
    379         mTvViewFrame = tvViewFrame;
    380         if (animate) {
    381             initTvAnimatorIfNeeded();
    382             if (mTvViewAnimator.isStarted()) {
    383                 // Cancel the current animation and start new one.
    384                 mTvViewAnimator.cancel();
    385                 mOldTvViewFrame = new FrameLayout.LayoutParams(mLastAnimatedTvViewFrame);
    386             } else {
    387                 mOldTvViewFrame = new FrameLayout.LayoutParams(oldTvViewFrame);
    388             }
    389             mTvViewAnimator.setObjectValues(mTvView.getTvViewLayoutParams(), layoutParams);
    390             mTvViewAnimator.setEvaluator(
    391                     new TypeEvaluator<FrameLayout.LayoutParams>() {
    392                         FrameLayout.LayoutParams lp;
    393 
    394                         @Override
    395                         public FrameLayout.LayoutParams evaluate(
    396                                 float fraction,
    397                                 FrameLayout.LayoutParams startValue,
    398                                 FrameLayout.LayoutParams endValue) {
    399                             if (lp == null) {
    400                                 lp = new FrameLayout.LayoutParams(0, 0);
    401                                 lp.gravity = startValue.gravity;
    402                             }
    403                             interpolateMargins(lp, startValue, endValue, fraction);
    404                             return lp;
    405                         }
    406                     });
    407             mTvViewAnimator.setInterpolator(
    408                     isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn);
    409             mTvViewAnimator.start();
    410         } else {
    411             if (mTvViewAnimator != null && mTvViewAnimator.isStarted()) {
    412                 // Continue the current animation.
    413                 // layoutParams will be applied when animation ends.
    414                 return;
    415             }
    416             // This block is also called when animation ends.
    417             if (isTvViewFullScreen()) {
    418                 // When this layout is for full screen, fix the surface size after layout to make
    419                 // resize animation smooth. During PIP size change, the multiple messages can be
    420                 // queued, if we don't remove MSG_SET_LAYOUT_PARAMS.
    421                 mHandler.removeMessages(MSG_SET_LAYOUT_PARAMS);
    422                 mHandler.obtainMessage(MSG_SET_LAYOUT_PARAMS, layoutParams).sendToTarget();
    423             } else {
    424                 mTvView.setTvViewLayoutParams(layoutParams);
    425                 mTvView.setLayoutParams(mTvViewFrame);
    426             }
    427         }
    428     }
    429 
    430     private void initTvAnimatorIfNeeded() {
    431         if (mTvViewAnimator != null) {
    432             return;
    433         }
    434 
    435         // TvViewAnimator animates TvView by repeatedly re-layouting TvView.
    436         // TvView includes a SurfaceView on which scale/translation effects do not work. Normally,
    437         // SurfaceView can be animated by changing left/top/right/bottom directly using
    438         // ObjectAnimator, although it would require calling getChildAt(0) against TvView (which is
    439         // supposed to be opaque). More importantly, this method does not work in case of TvView,
    440         // because TvView may request layout itself during animation and layout SurfaceView with
    441         // its own parameters when TvInputService requests to do so.
    442         mTvViewAnimator = new ObjectAnimator();
    443         mTvViewAnimator.setTarget(mTvView.getTvView());
    444         mTvViewAnimator.setProperty(
    445                 Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams"));
    446         mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration));
    447         mTvViewAnimator.addListener(
    448                 new AnimatorListenerAdapter() {
    449                     private boolean mCanceled = false;
    450 
    451                     @Override
    452                     public void onAnimationCancel(Animator animation) {
    453                         mCanceled = true;
    454                     }
    455 
    456                     @Override
    457                     public void onAnimationEnd(Animator animation) {
    458                         if (mCanceled) {
    459                             mCanceled = false;
    460                             return;
    461                         }
    462                         mHandler.post(
    463                                 new Runnable() {
    464                                     @Override
    465                                     public void run() {
    466                                         setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false);
    467                                     }
    468                                 });
    469                     }
    470                 });
    471         mTvViewAnimator.addUpdateListener(
    472                 new AnimatorUpdateListener() {
    473                     @Override
    474                     public void onAnimationUpdate(ValueAnimator animator) {
    475                         float fraction = animator.getAnimatedFraction();
    476                         mLastAnimatedTvViewFrame =
    477                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
    478                         interpolateMargins(
    479                                 mLastAnimatedTvViewFrame, mOldTvViewFrame, mTvViewFrame, fraction);
    480                         mTvView.setLayoutParams(mLastAnimatedTvViewFrame);
    481                     }
    482                 });
    483     }
    484 
    485     private void initBackgroundAnimatorIfNeeded() {
    486         if (mBackgroundAnimator != null) {
    487             return;
    488         }
    489 
    490         mBackgroundAnimator = new ObjectAnimator();
    491         mBackgroundAnimator.setTarget(mContentView);
    492         mBackgroundAnimator.setPropertyName("backgroundColor");
    493         mBackgroundAnimator.setDuration(
    494                 mResources.getInteger(R.integer.tvactivity_background_anim_duration));
    495         mBackgroundAnimator.addListener(
    496                 new AnimatorListenerAdapter() {
    497                     @Override
    498                     public void onAnimationEnd(Animator animation) {
    499                         mHandler.post(
    500                                 new Runnable() {
    501                                     @Override
    502                                     public void run() {
    503                                         mContentView.setBackgroundColor(mBackgroundColor);
    504                                     }
    505                                 });
    506                     }
    507                 });
    508     }
    509 
    510     private void applyDisplayMode(
    511             float videoDisplayAspectRatio, boolean animate, boolean forceUpdate) {
    512         if (videoDisplayAspectRatio <= 0f) {
    513             videoDisplayAspectRatio = (float) mWindowWidth / mWindowHeight;
    514         }
    515         if (mAppliedDisplayedMode == mDisplayMode
    516                 && mAppliedTvViewStartMargin == mTvViewStartMargin
    517                 && mAppliedTvViewEndMargin == mTvViewEndMargin
    518                 && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio)
    519                         < DISPLAY_ASPECT_RATIO_EPSILON) {
    520             if (!forceUpdate) {
    521                 return;
    522             }
    523         } else {
    524             mAppliedDisplayedMode = mDisplayMode;
    525             mAppliedTvViewStartMargin = mTvViewStartMargin;
    526             mAppliedTvViewEndMargin = mTvViewEndMargin;
    527             mAppliedVideoDisplayAspectRatio = videoDisplayAspectRatio;
    528         }
    529         int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin;
    530         int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth;
    531         int displayMode = mDisplayMode;
    532         float availableAreaRatio = 0;
    533         if (availableAreaWidth <= 0 || availableAreaHeight <= 0) {
    534             displayMode = DisplayMode.MODE_FULL;
    535             Log.w(
    536                     TAG,
    537                     "Some resolution info is missing during applyDisplayMode. ("
    538                             + "availableAreaWidth="
    539                             + availableAreaWidth
    540                             + ", availableAreaHeight="
    541                             + availableAreaHeight
    542                             + ")");
    543         } else {
    544             availableAreaRatio = (float) availableAreaWidth / availableAreaHeight;
    545         }
    546         FrameLayout.LayoutParams layoutParams =
    547                 new FrameLayout.LayoutParams(
    548                         0, 0, ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity);
    549         switch (displayMode) {
    550             case DisplayMode.MODE_ZOOM:
    551                 if (videoDisplayAspectRatio < availableAreaRatio) {
    552                     // Y axis will be clipped.
    553                     layoutParams.width = availableAreaWidth;
    554                     layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio);
    555                 } else {
    556                     // X axis will be clipped.
    557                     layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio);
    558                     layoutParams.height = availableAreaHeight;
    559                 }
    560                 break;
    561             case DisplayMode.MODE_NORMAL:
    562                 if (videoDisplayAspectRatio < availableAreaRatio) {
    563                     // X axis has black area.
    564                     layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio);
    565                     layoutParams.height = availableAreaHeight;
    566                 } else {
    567                     // Y axis has black area.
    568                     layoutParams.width = availableAreaWidth;
    569                     layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio);
    570                 }
    571                 break;
    572             case DisplayMode.MODE_FULL:
    573             default:
    574                 layoutParams.width = availableAreaWidth;
    575                 layoutParams.height = availableAreaHeight;
    576                 break;
    577         }
    578         // FrameLayout has an issue with centering when left and right margins differ.
    579         // So stick to Gravity.START | Gravity.CENTER_VERTICAL.
    580         int marginStart = (availableAreaWidth - layoutParams.width) / 2;
    581         layoutParams.setMarginStart(marginStart);
    582         int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2;
    583         FrameLayout.LayoutParams tvViewFrame =
    584                 createMarginLayoutParams(
    585                         mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
    586         setTvViewPosition(layoutParams, tvViewFrame, animate);
    587         setBackgroundColor(
    588                 mResources.getColor(
    589                         isTvViewFullScreen()
    590                                 ? R.color.tvactivity_background
    591                                 : R.color.tvactivity_background_on_shrunken_tvview,
    592                         null),
    593                 layoutParams,
    594                 animate);
    595 
    596         // Update the current display mode.
    597         mTvOptionsManager.onDisplayModeChanged(displayMode);
    598     }
    599 
    600     private static int interpolate(int start, int end, float fraction) {
    601         return (int) (start + (end - start) * fraction);
    602     }
    603 
    604     private static void interpolateMargins(
    605             MarginLayoutParams out,
    606             MarginLayoutParams startValue,
    607             MarginLayoutParams endValue,
    608             float fraction) {
    609         out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction);
    610         out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction);
    611         out.setMarginStart(
    612                 interpolate(startValue.getMarginStart(), endValue.getMarginStart(), fraction));
    613         out.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction));
    614         out.width = interpolate(startValue.width, endValue.width, fraction);
    615         out.height = interpolate(startValue.height, endValue.height, fraction);
    616     }
    617 
    618     private FrameLayout.LayoutParams createMarginLayoutParams(
    619             int startMargin, int endMargin, int topMargin, int bottomMargin) {
    620         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(0, 0);
    621         lp.setMarginStart(startMargin);
    622         lp.setMarginEnd(endMargin);
    623         lp.topMargin = topMargin;
    624         lp.bottomMargin = bottomMargin;
    625         lp.width = mWindowWidth - startMargin - endMargin;
    626         lp.height = mWindowHeight - topMargin - bottomMargin;
    627         return lp;
    628     }
    629 }
    630