TvViewUiManager.java revision 944779887775bd950cf1abf348d2df461593f6ab
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
17package com.android.tv.ui;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ArgbEvaluator;
22import android.animation.ObjectAnimator;
23import android.animation.TimeInterpolator;
24import android.animation.TypeEvaluator;
25import android.animation.ValueAnimator;
26import android.animation.ValueAnimator.AnimatorUpdateListener;
27import android.app.Activity;
28import android.content.Context;
29import android.content.SharedPreferences;
30import android.content.res.Resources;
31import android.graphics.Point;
32import android.hardware.display.DisplayManager;
33import android.os.Build;
34import android.os.Handler;
35import android.os.Message;
36import android.preference.PreferenceManager;
37import android.util.Log;
38import android.util.Property;
39import android.view.Display;
40import android.view.ViewGroup;
41import android.view.ViewGroup.LayoutParams;
42import android.view.ViewGroup.MarginLayoutParams;
43import android.view.animation.AnimationUtils;
44import android.widget.FrameLayout;
45import com.android.tv.R;
46import com.android.tv.TvFeatures;
47import com.android.tv.TvOptionsManager;
48import com.android.tv.data.DisplayMode;
49import 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 */
55public 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