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