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.TvOptionsManager;
47 import com.android.tv.data.DisplayMode;
48 import com.android.tv.features.TvFeatures;
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 
TvViewUiManager( Context context, TunableTvView tvView, FrameLayout contentView, TvOptionsManager tvOptionManager)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 
onConfigurationChanged(final int windowWidth, final int windowHeight)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      */
initAnimatorIfNeeded()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      */
startShrunkenTvView()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      */
endShrunkenTvView()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. */
isUnderShrunkenTvView()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      */
isDisplayModeAvailable(int displayMode)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. */
getDisplayMode()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      */
setDisplayMode(int displayMode, boolean storeInPreference, boolean animate)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. */
restoreDisplayMode(boolean animate)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. */
updateTvAspectRatio()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. */
fadeInTvView()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. */
fadeOutTvView(Runnable postAction)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. */
setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin)310     private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
311         mTvViewStartMargin = tvViewStartMargin;
312         mTvViewEndMargin = tvViewEndMargin;
313     }
314 
isTvViewFullScreen()315     private boolean isTvViewFullScreen() {
316         return mTvViewStartMargin == 0 && mTvViewEndMargin == 0;
317     }
318 
setBackgroundColor( int color, FrameLayout.LayoutParams targetLayoutParams, boolean animate)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 
setTvViewPosition( final FrameLayout.LayoutParams layoutParams, FrameLayout.LayoutParams tvViewFrame, boolean animate)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 
initTvAnimatorIfNeeded()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                                 () -> setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false));
464                     }
465                 });
466         mTvViewAnimator.addUpdateListener(
467                 new AnimatorUpdateListener() {
468                     @Override
469                     public void onAnimationUpdate(ValueAnimator animator) {
470                         float fraction = animator.getAnimatedFraction();
471                         mLastAnimatedTvViewFrame =
472                                 (FrameLayout.LayoutParams) mTvView.getLayoutParams();
473                         interpolateMargins(
474                                 mLastAnimatedTvViewFrame, mOldTvViewFrame, mTvViewFrame, fraction);
475                         mTvView.setLayoutParams(mLastAnimatedTvViewFrame);
476                     }
477                 });
478     }
479 
initBackgroundAnimatorIfNeeded()480     private void initBackgroundAnimatorIfNeeded() {
481         if (mBackgroundAnimator != null) {
482             return;
483         }
484 
485         mBackgroundAnimator = new ObjectAnimator();
486         mBackgroundAnimator.setTarget(mContentView);
487         mBackgroundAnimator.setPropertyName("backgroundColor");
488         mBackgroundAnimator.setDuration(
489                 mResources.getInteger(R.integer.tvactivity_background_anim_duration));
490         mBackgroundAnimator.addListener(
491                 new AnimatorListenerAdapter() {
492                     @Override
493                     public void onAnimationEnd(Animator animation) {
494                         mHandler.post(() -> mContentView.setBackgroundColor(mBackgroundColor));
495                     }
496                 });
497     }
498 
applyDisplayMode( float videoDisplayAspectRatio, boolean animate, boolean forceUpdate)499     private void applyDisplayMode(
500             float videoDisplayAspectRatio, boolean animate, boolean forceUpdate) {
501         if (videoDisplayAspectRatio <= 0f) {
502             videoDisplayAspectRatio = (float) mWindowWidth / mWindowHeight;
503         }
504         if (mAppliedDisplayedMode == mDisplayMode
505                 && mAppliedTvViewStartMargin == mTvViewStartMargin
506                 && mAppliedTvViewEndMargin == mTvViewEndMargin
507                 && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio)
508                         < DISPLAY_ASPECT_RATIO_EPSILON) {
509             if (!forceUpdate) {
510                 return;
511             }
512         } else {
513             mAppliedDisplayedMode = mDisplayMode;
514             mAppliedTvViewStartMargin = mTvViewStartMargin;
515             mAppliedTvViewEndMargin = mTvViewEndMargin;
516             mAppliedVideoDisplayAspectRatio = videoDisplayAspectRatio;
517         }
518         int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin;
519         int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth;
520         int displayMode = mDisplayMode;
521         float availableAreaRatio = 0;
522         if (availableAreaWidth <= 0 || availableAreaHeight <= 0) {
523             displayMode = DisplayMode.MODE_FULL;
524             Log.w(
525                     TAG,
526                     "Some resolution info is missing during applyDisplayMode. ("
527                             + "availableAreaWidth="
528                             + availableAreaWidth
529                             + ", availableAreaHeight="
530                             + availableAreaHeight
531                             + ")");
532         } else {
533             availableAreaRatio = (float) availableAreaWidth / availableAreaHeight;
534         }
535         FrameLayout.LayoutParams layoutParams =
536                 new FrameLayout.LayoutParams(
537                         0, 0, ((FrameLayout.LayoutParams) mTvView.getTvViewLayoutParams()).gravity);
538         switch (displayMode) {
539             case DisplayMode.MODE_ZOOM:
540                 if (videoDisplayAspectRatio < availableAreaRatio) {
541                     // Y axis will be clipped.
542                     layoutParams.width = availableAreaWidth;
543                     layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio);
544                 } else {
545                     // X axis will be clipped.
546                     layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio);
547                     layoutParams.height = availableAreaHeight;
548                 }
549                 break;
550             case DisplayMode.MODE_NORMAL:
551                 if (videoDisplayAspectRatio < availableAreaRatio) {
552                     // X axis has black area.
553                     layoutParams.width = Math.round(availableAreaHeight * videoDisplayAspectRatio);
554                     layoutParams.height = availableAreaHeight;
555                 } else {
556                     // Y axis has black area.
557                     layoutParams.width = availableAreaWidth;
558                     layoutParams.height = Math.round(availableAreaWidth / videoDisplayAspectRatio);
559                 }
560                 break;
561             case DisplayMode.MODE_FULL:
562             default:
563                 layoutParams.width = availableAreaWidth;
564                 layoutParams.height = availableAreaHeight;
565                 break;
566         }
567         // FrameLayout has an issue with centering when left and right margins differ.
568         // So stick to Gravity.START | Gravity.CENTER_VERTICAL.
569         int marginStart = (availableAreaWidth - layoutParams.width) / 2;
570         layoutParams.setMarginStart(marginStart);
571         int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2;
572         FrameLayout.LayoutParams tvViewFrame =
573                 createMarginLayoutParams(
574                         mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
575         setTvViewPosition(layoutParams, tvViewFrame, animate);
576         setBackgroundColor(
577                 mResources.getColor(
578                         isTvViewFullScreen()
579                                 ? R.color.tvactivity_background
580                                 : R.color.tvactivity_background_on_shrunken_tvview,
581                         null),
582                 layoutParams,
583                 animate);
584 
585         // Update the current display mode.
586         mTvOptionsManager.onDisplayModeChanged(displayMode);
587     }
588 
interpolate(int start, int end, float fraction)589     private static int interpolate(int start, int end, float fraction) {
590         return (int) (start + (end - start) * fraction);
591     }
592 
interpolateMargins( MarginLayoutParams out, MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction)593     private static void interpolateMargins(
594             MarginLayoutParams out,
595             MarginLayoutParams startValue,
596             MarginLayoutParams endValue,
597             float fraction) {
598         out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction);
599         out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction);
600         out.setMarginStart(
601                 interpolate(startValue.getMarginStart(), endValue.getMarginStart(), fraction));
602         out.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction));
603         out.width = interpolate(startValue.width, endValue.width, fraction);
604         out.height = interpolate(startValue.height, endValue.height, fraction);
605     }
606 
createMarginLayoutParams( int startMargin, int endMargin, int topMargin, int bottomMargin)607     private FrameLayout.LayoutParams createMarginLayoutParams(
608             int startMargin, int endMargin, int topMargin, int bottomMargin) {
609         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(0, 0);
610         lp.setMarginStart(startMargin);
611         lp.setMarginEnd(endMargin);
612         lp.topMargin = topMargin;
613         lp.bottomMargin = bottomMargin;
614         lp.width = mWindowWidth - startMargin - endMargin;
615         lp.height = mWindowHeight - topMargin - bottomMargin;
616         return lp;
617     }
618 }
619