• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
TvViewUiManager(Context context, TunableTvView tvView, FrameLayout contentView, TvOptionsManager tvOptionManager)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 
onConfigurationChanged(final int windowWidth, final int windowHeight)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      */
initAnimatorIfNeeded()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      */
startShrunkenTvView()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      */
endShrunkenTvView()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      */
isUnderShrunkenTvView()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      */
isDisplayModeAvailable(int displayMode)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      */
getDisplayMode()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      */
setDisplayMode(int displayMode, boolean storeInPreference, boolean animate)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      */
restoreDisplayMode(boolean animate)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      */
updateTvAspectRatio()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      */
fadeInTvView()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      */
fadeOutTvView(Runnable postAction)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      */
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(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 
setTvViewPosition(final FrameLayout.LayoutParams layoutParams, FrameLayout.LayoutParams tvViewFrame, boolean animate)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 
initTvAnimatorIfNeeded()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 
initBackgroundAnimatorIfNeeded()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 
applyDisplayMode(float videoDisplayAspectRatio, boolean animate, boolean forceUpdate)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 
interpolate(int start, int end, float fraction)563     private static int interpolate(int start, int end, float fraction) {
564         return (int) (start + (end - start) * fraction);
565     }
566 
interpolateMargins(MarginLayoutParams out, MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction)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 
createMarginLayoutParams( int startMargin, int endMargin, int topMargin, int bottomMargin)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 }