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.annotation.SuppressLint;
     28 import android.app.Activity;
     29 import android.content.Context;
     30 import android.content.SharedPreferences;
     31 import android.content.res.Resources;
     32 import android.graphics.Point;
     33 import android.hardware.display.DisplayManager;
     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.Gravity;
     41 import android.view.View;
     42 import android.view.ViewGroup;
     43 import android.view.ViewGroup.LayoutParams;
     44 import android.view.ViewGroup.MarginLayoutParams;
     45 import android.view.animation.AnimationUtils;
     46 import android.widget.FrameLayout;
     47 
     48 import com.android.tv.Features;
     49 import com.android.tv.R;
     50 import com.android.tv.TvOptionsManager;
     51 import com.android.tv.data.DisplayMode;
     52 import com.android.tv.util.TvSettings;
     53 
     54 /**
     55  * The TvViewUiManager is responsible for handling UI layouting and animation of main and PIP
     56  * TvViews. It also control the settings regarding TvView UI such as display mode, PIP layout,
     57  * and PIP size.
     58  */
     59 public class TvViewUiManager {
     60     private static final String TAG = "TvViewManager";
     61     private static final boolean DEBUG = false;
     62 
     63     private static final float DISPLAY_MODE_EPSILON = 0.001f;
     64     private static final float DISPLAY_ASPECT_RATIO_EPSILON = 0.01f;
     65 
     66     private static final int MSG_SET_LAYOUT_PARAMS = 1000;
     67 
     68     private final Context mContext;
     69     private final Resources mResources;
     70     private final FrameLayout mContentView;
     71     private final TunableTvView mTvView;
     72     private final TunableTvView mPipView;
     73     private final TvOptionsManager mTvOptionsManager;
     74     private final int mTvViewPapWidth;
     75     private final int mTvViewShrunkenStartMargin;
     76     private final int mTvViewShrunkenEndMargin;
     77     private final int mTvViewPapStartMargin;
     78     private final int mTvViewPapEndMargin;
     79     private int mWindowWidth;
     80     private int mWindowHeight;
     81     private final int mPipViewHorizontalMargin;
     82     private final int mPipViewTopMargin;
     83     private final int mPipViewBottomMargin;
     84     private final SharedPreferences mSharedPreferences;
     85     private final TimeInterpolator mLinearOutSlowIn;
     86     private final TimeInterpolator mFastOutLinearIn;
     87     private final Handler mHandler = new Handler() {
     88         @Override
     89         public void handleMessage(Message msg) {
     90             switch(msg.what) {
     91                 case MSG_SET_LAYOUT_PARAMS:
     92                     FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) msg.obj;
     93                     if (DEBUG) {
     94                         Log.d(TAG, "setFixedSize: w=" + layoutParams.width + " h="
     95                                 + layoutParams.height);
     96                     }
     97                     mTvView.setLayoutParams(layoutParams);
     98                     // Smooth PIP size change, we don't change surface size when
     99                     // isInPictureInPictureMode is true.
    100                     if (!Features.PICTURE_IN_PICTURE.isEnabled(mContext)
    101                             || !((Activity) mContext).isInPictureInPictureMode()) {
    102                         mTvView.setFixedSurfaceSize(layoutParams.width, layoutParams.height);
    103                     }
    104                     break;
    105             }
    106         }
    107     };
    108     private int mDisplayMode;
    109     // Used to restore the previous state from ShrunkenTvView state.
    110     private int mTvViewStartMarginBeforeShrunken;
    111     private int mTvViewEndMarginBeforeShrunken;
    112     private int mDisplayModeBeforeShrunken;
    113     private boolean mIsUnderShrunkenTvView;
    114     private int mTvViewStartMargin;
    115     private int mTvViewEndMargin;
    116     private int mPipLayout;
    117     private int mPipSize;
    118     private boolean mPipStarted;
    119     private ObjectAnimator mTvViewAnimator;
    120     private FrameLayout.LayoutParams mTvViewLayoutParams;
    121     // TV view's position when the display mode is FULL. It is used to compute PIP location relative
    122     // to TV view's position.
    123     private MarginLayoutParams mTvViewFrame;
    124     private MarginLayoutParams mLastAnimatedTvViewFrame;
    125     private MarginLayoutParams mOldTvViewFrame;
    126     private ObjectAnimator mBackgroundAnimator;
    127     private int mBackgroundColor;
    128     private int mAppliedDisplayedMode = DisplayMode.MODE_NOT_DEFINED;
    129     private int mAppliedTvViewStartMargin;
    130     private int mAppliedTvViewEndMargin;
    131     private float mAppliedVideoDisplayAspectRatio;
    132 
    133     public TvViewUiManager(Context context, TunableTvView tvView, TunableTvView pipView,
    134             FrameLayout contentView, TvOptionsManager tvOptionManager) {
    135         mContext = context;
    136         mResources = mContext.getResources();
    137         mTvView = tvView;
    138         mPipView = pipView;
    139         mContentView = contentView;
    140         mTvOptionsManager = tvOptionManager;
    141 
    142         DisplayManager displayManager = (DisplayManager) mContext
    143                 .getSystemService(Context.DISPLAY_SERVICE);
    144         Display display = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
    145         Point size = new Point();
    146         display.getSize(size);
    147         mWindowWidth = size.x;
    148         mWindowHeight = size.y;
    149 
    150         // Have an assumption that PIP and TvView Shrinking happens only in full screen.
    151         mTvViewShrunkenStartMargin = mResources
    152                 .getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_start);
    153         mTvViewShrunkenEndMargin =
    154                 mResources.getDimensionPixelOffset(R.dimen.shrunken_tvview_margin_end)
    155                         + mResources.getDimensionPixelSize(R.dimen.side_panel_width);
    156         int papMarginHorizontal = mResources
    157                 .getDimensionPixelOffset(R.dimen.papview_margin_horizontal);
    158         int papSpacing = mResources.getDimensionPixelOffset(R.dimen.papview_spacing);
    159         mTvViewPapWidth = (mWindowWidth - papSpacing) / 2 - papMarginHorizontal;
    160         mTvViewPapStartMargin = papMarginHorizontal + mTvViewPapWidth + papSpacing;
    161         mTvViewPapEndMargin = papMarginHorizontal;
    162         mTvViewFrame = createMarginLayoutParams(0, 0, 0, 0);
    163 
    164         mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
    165 
    166         mLinearOutSlowIn = AnimationUtils
    167                 .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
    168         mFastOutLinearIn = AnimationUtils
    169                 .loadInterpolator(mContext, android.R.interpolator.fast_out_linear_in);
    170 
    171         mPipViewHorizontalMargin = mResources
    172                 .getDimensionPixelOffset(R.dimen.pipview_margin_horizontal);
    173         mPipViewTopMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_top);
    174         mPipViewBottomMargin = mResources.getDimensionPixelOffset(R.dimen.pipview_margin_bottom);
    175     }
    176 
    177     public void onConfigurationChanged(final int windowWidth, final int windowHeight) {
    178         if (windowWidth > 0 && windowHeight > 0) {
    179             if (mWindowWidth != windowWidth || mWindowHeight != windowHeight) {
    180                 mWindowWidth = windowWidth;
    181                 mWindowHeight = windowHeight;
    182                 applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, true);
    183             }
    184         }
    185     }
    186 
    187     /**
    188      * Initializes animator in advance of using the animator to improve animation performance.
    189      * For fast first tune, it is not expected to be called in Activity.onCreate, but called
    190      * a few seconds later after onCreate.
    191      */
    192     public void initAnimatorIfNeeded() {
    193         initTvAnimatorIfNeeded();
    194         initBackgroundAnimatorIfNeeded();
    195     }
    196 
    197     /**
    198      * It is called when shrunken TvView is desired, such as EditChannelFragment and
    199      * ChannelsLockedFragment.
    200      */
    201     public void startShrunkenTvView() {
    202         mIsUnderShrunkenTvView = true;
    203 
    204         mTvViewStartMarginBeforeShrunken = mTvViewStartMargin;
    205         mTvViewEndMarginBeforeShrunken = mTvViewEndMargin;
    206         if (mPipStarted && getPipLayout() == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
    207             float sidePanelWidth = mResources.getDimensionPixelOffset(R.dimen.side_panel_width);
    208             float factor = 1.0f - sidePanelWidth / mWindowWidth;
    209             int startMargin = (int) (mTvViewPapStartMargin * factor);
    210             int endMargin = (int) (mTvViewPapEndMargin * factor + sidePanelWidth);
    211             setTvViewMargin(startMargin, endMargin);
    212         } else {
    213             setTvViewMargin(mTvViewShrunkenStartMargin, mTvViewShrunkenEndMargin);
    214         }
    215         mDisplayModeBeforeShrunken = setDisplayMode(DisplayMode.MODE_NORMAL, false, true);
    216     }
    217 
    218     /**
    219      * It is called when shrunken TvView is no longer desired, such as EditChannelFragment and
    220      * ChannelsLockedFragment.
    221      */
    222     public void endShrunkenTvView() {
    223         mIsUnderShrunkenTvView = false;
    224         setTvViewMargin(mTvViewStartMarginBeforeShrunken, mTvViewEndMarginBeforeShrunken);
    225         setDisplayMode(mDisplayModeBeforeShrunken, false, true);
    226     }
    227 
    228     /**
    229      * Returns true, if TvView is shrunken.
    230      */
    231     public boolean isUnderShrunkenTvView() {
    232         return mIsUnderShrunkenTvView;
    233     }
    234 
    235     /**
    236      * Returns true, if {@code displayMode} is available now. If screen ratio is matched to
    237      * video ratio, other display modes than {@link DisplayMode#MODE_NORMAL} are not available.
    238      */
    239     public boolean isDisplayModeAvailable(int displayMode) {
    240         if (displayMode == DisplayMode.MODE_NORMAL) {
    241             return true;
    242         }
    243 
    244         int viewWidth = mContentView.getWidth();
    245         int viewHeight = mContentView.getHeight();
    246 
    247         float videoDisplayAspectRatio = mTvView.getVideoDisplayAspectRatio();
    248         if (viewWidth <= 0 || viewHeight <= 0 || videoDisplayAspectRatio <= 0f) {
    249             Log.w(TAG, "Video size is currently unavailable");
    250             if (DEBUG) {
    251                 Log.d(TAG, "isDisplayModeAvailable: "
    252                         + "viewWidth=" + viewWidth
    253                         + ", viewHeight=" + viewHeight
    254                         + ", videoDisplayAspectRatio=" + videoDisplayAspectRatio
    255                 );
    256             }
    257             return false;
    258         }
    259 
    260         float viewRatio = viewWidth / (float) viewHeight;
    261         return Math.abs(viewRatio - videoDisplayAspectRatio) >= DISPLAY_MODE_EPSILON;
    262     }
    263 
    264     /**
    265      * Returns a constant defined in DisplayMode.
    266      */
    267     public int getDisplayMode() {
    268         if (isDisplayModeAvailable(mDisplayMode)) {
    269             return mDisplayMode;
    270         }
    271         return DisplayMode.MODE_NORMAL;
    272     }
    273 
    274     /**
    275      * Sets the display mode to the given value.
    276      *
    277      * @return the previous display mode.
    278      */
    279     public int setDisplayMode(int displayMode, boolean storeInPreference, boolean animate) {
    280         int prev = mDisplayMode;
    281         mDisplayMode = displayMode;
    282         if (storeInPreference) {
    283             mSharedPreferences.edit().putInt(TvSettings.PREF_DISPLAY_MODE, displayMode).apply();
    284         }
    285         applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), animate, false);
    286         return prev;
    287     }
    288 
    289     /**
    290      * Restores the display mode to the display mode stored in preference.
    291      */
    292     public void restoreDisplayMode(boolean animate) {
    293         int displayMode = mSharedPreferences
    294                 .getInt(TvSettings.PREF_DISPLAY_MODE, DisplayMode.MODE_NORMAL);
    295         setDisplayMode(displayMode, false, animate);
    296     }
    297 
    298     /**
    299      * Updates TvView. It is called when video resolution is updated.
    300      */
    301     public void updateTvView() {
    302         applyDisplayMode(mTvView.getVideoDisplayAspectRatio(), false, false);
    303         if (mTvView.isVideoAvailable() && mTvView.isFadedOut()) {
    304             mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
    305                     mFastOutLinearIn, null);
    306         }
    307     }
    308 
    309     /**
    310      * Fades in TvView.
    311      */
    312     public void fadeInTvView() {
    313         if (mTvView.isFadedOut()) {
    314             mTvView.fadeIn(mResources.getInteger(R.integer.tvview_fade_in_duration),
    315                     mFastOutLinearIn, null);
    316         }
    317     }
    318 
    319     /**
    320      * Fades out TvView.
    321      */
    322     public void fadeOutTvView(Runnable postAction) {
    323         if (!mTvView.isFadedOut()) {
    324             mTvView.fadeOut(mResources.getInteger(R.integer.tvview_fade_out_duration),
    325                     mLinearOutSlowIn, postAction);
    326         }
    327     }
    328 
    329     /**
    330      * Returns the current PIP layout. The layout should be one of
    331      * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT},
    332      * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and
    333      * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}.
    334      */
    335     public int getPipLayout() {
    336         return mPipLayout;
    337     }
    338 
    339     /**
    340      * Sets the PIP layout. The layout should be one of
    341      * {@link TvSettings#PIP_LAYOUT_BOTTOM_RIGHT}, {@link TvSettings#PIP_LAYOUT_TOP_RIGHT},
    342      * {@link TvSettings#PIP_LAYOUT_TOP_LEFT}, {@link TvSettings#PIP_LAYOUT_BOTTOM_LEFT} and
    343      * {@link TvSettings#PIP_LAYOUT_SIDE_BY_SIDE}.
    344      *
    345      * @param storeInPreference if true, the stored value will be restored by
    346      *                          {@link #restorePipLayout()}.
    347      */
    348     public void setPipLayout(int pipLayout, boolean storeInPreference) {
    349         mPipLayout = pipLayout;
    350         if (storeInPreference) {
    351             TvSettings.setPipLayout(mContext, pipLayout);
    352         }
    353         updatePipView(mTvViewFrame);
    354         if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
    355             setTvViewMargin(mTvViewPapStartMargin, mTvViewPapEndMargin);
    356             setDisplayMode(DisplayMode.MODE_NORMAL, false, false);
    357         } else {
    358             setTvViewMargin(0, 0);
    359             restoreDisplayMode(false);
    360         }
    361         mTvOptionsManager.onPipLayoutChanged(pipLayout);
    362     }
    363 
    364     /**
    365      * Restores the PIP layout which {@link #setPipLayout} lastly stores.
    366      */
    367     public void restorePipLayout() {
    368         setPipLayout(TvSettings.getPipLayout(mContext), false);
    369     }
    370 
    371     /**
    372      * Called when PIP is started.
    373      */
    374     public void onPipStart() {
    375         mPipStarted = true;
    376         updatePipView();
    377         mPipView.setVisibility(View.VISIBLE);
    378     }
    379 
    380     /**
    381      * Called when PIP is stopped.
    382      */
    383     public void onPipStop() {
    384         setTvViewMargin(0, 0);
    385         mPipView.setVisibility(View.GONE);
    386         mPipStarted = false;
    387     }
    388 
    389     /**
    390      * Called when PIP is resumed.
    391      */
    392     public void showPipForResume() {
    393         mPipView.setVisibility(View.VISIBLE);
    394     }
    395 
    396     /**
    397      * Called when PIP is paused.
    398      */
    399     public void hidePipForPause() {
    400         if (mPipLayout != TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
    401             mPipView.setVisibility(View.GONE);
    402         }
    403     }
    404 
    405     /**
    406      * Updates PIP view. It is usually called, when video resolution in PIP is updated.
    407      */
    408     public void updatePipView() {
    409         updatePipView(mTvViewFrame);
    410     }
    411 
    412     /**
    413      * Returns the size of the PIP view.
    414      */
    415     public int getPipSize() {
    416         return mPipSize;
    417     }
    418 
    419     /**
    420      * Sets PIP size and applies it immediately.
    421      *
    422      * @param pipSize           PIP size. The value should be one of {@link TvSettings#PIP_SIZE_BIG}
    423      *                          and {@link TvSettings#PIP_SIZE_SMALL}.
    424      * @param storeInPreference if true, the stored value will be restored by
    425      *                          {@link #restorePipSize()}.
    426      */
    427     public void setPipSize(int pipSize, boolean storeInPreference) {
    428         mPipSize = pipSize;
    429         if (storeInPreference) {
    430             TvSettings.setPipSize(mContext, pipSize);
    431         }
    432         updatePipView(mTvViewFrame);
    433         mTvOptionsManager.onPipSizeChanged(pipSize);
    434     }
    435 
    436     /**
    437      * Restores the PIP size which {@link #setPipSize} lastly stores.
    438      */
    439     public void restorePipSize() {
    440         setPipSize(TvSettings.getPipSize(mContext), false);
    441     }
    442 
    443     /**
    444      * This margins will be applied when applyDisplayMode is called.
    445      */
    446     private void setTvViewMargin(int tvViewStartMargin, int tvViewEndMargin) {
    447         mTvViewStartMargin = tvViewStartMargin;
    448         mTvViewEndMargin = tvViewEndMargin;
    449     }
    450 
    451     private boolean isTvViewFullScreen() {
    452         return mTvViewStartMargin == 0 && mTvViewEndMargin == 0;
    453     }
    454 
    455     private void setBackgroundColor(int color, FrameLayout.LayoutParams targetLayoutParams,
    456             boolean animate) {
    457         if (animate) {
    458             initBackgroundAnimatorIfNeeded();
    459             if (mBackgroundAnimator.isStarted()) {
    460                 // Cancel the current animation and start new one.
    461                 mBackgroundAnimator.cancel();
    462             }
    463 
    464             int decorViewWidth = mContentView.getWidth();
    465             int decorViewHeight = mContentView.getHeight();
    466             boolean hasPillarBox = mTvView.getWidth() != decorViewWidth
    467                     || mTvView.getHeight() != decorViewHeight;
    468             boolean willHavePillarBox = ((targetLayoutParams.width != LayoutParams.MATCH_PARENT)
    469                     && targetLayoutParams.width != decorViewWidth) || (
    470                     (targetLayoutParams.height != LayoutParams.MATCH_PARENT)
    471                             && targetLayoutParams.height != decorViewHeight);
    472 
    473             if (!isTvViewFullScreen() && !hasPillarBox) {
    474                 // If there is no pillar box, no animation is needed.
    475                 mContentView.setBackgroundColor(color);
    476             } else if (!isTvViewFullScreen() || willHavePillarBox) {
    477                 mBackgroundAnimator.setIntValues(mBackgroundColor, color);
    478                 mBackgroundAnimator.setEvaluator(new ArgbEvaluator());
    479                 mBackgroundAnimator.setInterpolator(mFastOutLinearIn);
    480                 mBackgroundAnimator.start();
    481             }
    482             // In the 'else' case (TV activity is getting out of the shrunken tv view mode and will
    483             // have a pillar box), we keep the background color and don't show the animation.
    484         } else {
    485             mContentView.setBackgroundColor(color);
    486         }
    487         mBackgroundColor = color;
    488     }
    489 
    490     private void setTvViewPosition(final FrameLayout.LayoutParams layoutParams,
    491             MarginLayoutParams tvViewFrame, boolean animate) {
    492         if (DEBUG) {
    493             Log.d(TAG, "setTvViewPosition: w=" + layoutParams.width + " h=" + layoutParams.height
    494                     + " s=" + layoutParams.getMarginStart() + " t=" + layoutParams.topMargin
    495                     + " e=" + layoutParams.getMarginEnd() + " b=" + layoutParams.bottomMargin
    496                     + " animate=" + animate);
    497         }
    498         MarginLayoutParams oldTvViewFrame = mTvViewFrame;
    499         mTvViewLayoutParams = layoutParams;
    500         mTvViewFrame = tvViewFrame;
    501         if (animate) {
    502             initTvAnimatorIfNeeded();
    503             if (mTvViewAnimator.isStarted()) {
    504                 // Cancel the current animation and start new one.
    505                 mTvViewAnimator.cancel();
    506                 mOldTvViewFrame = mLastAnimatedTvViewFrame;
    507             } else {
    508                 mOldTvViewFrame = oldTvViewFrame;
    509             }
    510             mTvViewAnimator.setObjectValues(mTvView.getLayoutParams(), layoutParams);
    511             mTvViewAnimator.setEvaluator(new TypeEvaluator<FrameLayout.LayoutParams>() {
    512                 FrameLayout.LayoutParams lp;
    513                 @Override
    514                 public FrameLayout.LayoutParams evaluate(float fraction,
    515                         FrameLayout.LayoutParams startValue, FrameLayout.LayoutParams endValue) {
    516                     if (lp == null) {
    517                         lp = new FrameLayout.LayoutParams(0, 0);
    518                         lp.gravity = startValue.gravity;
    519                     }
    520                     interpolateMarginsRelative(lp, startValue, endValue, fraction);
    521                     return lp;
    522                 }
    523             });
    524             mTvViewAnimator
    525                     .setInterpolator(isTvViewFullScreen() ? mFastOutLinearIn : mLinearOutSlowIn);
    526             mTvViewAnimator.start();
    527         } else {
    528             if (mTvViewAnimator != null && mTvViewAnimator.isStarted()) {
    529                 // Continue the current animation.
    530                 // layoutParams will be applied when animation ends.
    531                 return;
    532             }
    533             // This block is also called when animation ends.
    534             if (isTvViewFullScreen()) {
    535                 // When this layout is for full screen, fix the surface size after layout to make
    536                 // resize animation smooth. During PIP size change, the multiple messages can be
    537                 // queued, if we don't remove MSG_SET_LAYOUT_PARAMS.
    538                 mHandler.removeMessages(MSG_SET_LAYOUT_PARAMS);
    539                 mHandler.obtainMessage(MSG_SET_LAYOUT_PARAMS, layoutParams).sendToTarget();
    540             } else {
    541                 mTvView.setLayoutParams(layoutParams);
    542             }
    543             updatePipView(mTvViewFrame);
    544         }
    545     }
    546 
    547     /**
    548      * The redlines assume that the ratio of the TV screen is 16:9. If the radio is not 16:9, the
    549      * layout of PAP can be broken.
    550      */
    551     @SuppressLint("RtlHardcoded")
    552     private void updatePipView(MarginLayoutParams tvViewFrame) {
    553         if (!mPipStarted) {
    554             return;
    555         }
    556         int width;
    557         int height;
    558         int startMargin;
    559         int endMargin;
    560         int topMargin;
    561         int bottomMargin;
    562         int gravity;
    563 
    564         if (mPipLayout == TvSettings.PIP_LAYOUT_SIDE_BY_SIDE) {
    565             gravity = Gravity.CENTER_VERTICAL | Gravity.START;
    566             height = tvViewFrame.height;
    567             float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio();
    568             if (videoDisplayAspectRatio <= 0f) {
    569                 width = tvViewFrame.width;
    570             } else {
    571                 width = (int) (height * videoDisplayAspectRatio);
    572                 if (width > tvViewFrame.width) {
    573                     width = tvViewFrame.width;
    574                 }
    575             }
    576             startMargin = mResources.getDimensionPixelOffset(R.dimen.papview_margin_horizontal)
    577                     * tvViewFrame.width / mTvViewPapWidth + (tvViewFrame.width - width) / 2;
    578             endMargin = 0;
    579             topMargin = 0;
    580             bottomMargin = 0;
    581         } else {
    582             int tvViewWidth = tvViewFrame.width;
    583             int tvViewHeight = tvViewFrame.height;
    584             int tvStartMargin = tvViewFrame.getMarginStart();
    585             int tvEndMargin = tvViewFrame.getMarginEnd();
    586             int tvTopMargin = tvViewFrame.topMargin;
    587             int tvBottomMargin = tvViewFrame.bottomMargin;
    588             float horizontalScaleFactor = (float) tvViewWidth / mWindowWidth;
    589             float verticalScaleFactor = (float) tvViewHeight / mWindowHeight;
    590 
    591             int maxWidth;
    592             if (mPipSize == TvSettings.PIP_SIZE_SMALL) {
    593                 maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_width)
    594                         * horizontalScaleFactor);
    595                 height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_small_size_height)
    596                         * verticalScaleFactor);
    597             } else if (mPipSize == TvSettings.PIP_SIZE_BIG) {
    598                 maxWidth = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_width)
    599                         * horizontalScaleFactor);
    600                 height = (int) (mResources.getDimensionPixelSize(R.dimen.pipview_large_size_height)
    601                         * verticalScaleFactor);
    602             } else {
    603                 throw new IllegalArgumentException("Invalid PIP size: " + mPipSize);
    604             }
    605             float videoDisplayAspectRatio = mPipView.getVideoDisplayAspectRatio();
    606             if (videoDisplayAspectRatio <= 0f) {
    607                 width = maxWidth;
    608             } else {
    609                 width = (int) (height * videoDisplayAspectRatio);
    610                 if (width > maxWidth) {
    611                     width = maxWidth;
    612                 }
    613             }
    614 
    615             startMargin = tvStartMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor);
    616             endMargin = tvEndMargin + (int) (mPipViewHorizontalMargin * horizontalScaleFactor);
    617             topMargin = tvTopMargin + (int) (mPipViewTopMargin * verticalScaleFactor);
    618             bottomMargin = tvBottomMargin + (int) (mPipViewBottomMargin * verticalScaleFactor);
    619 
    620             switch (mPipLayout) {
    621                 case TvSettings.PIP_LAYOUT_TOP_LEFT:
    622                     gravity = Gravity.TOP | Gravity.LEFT;
    623                     break;
    624                 case TvSettings.PIP_LAYOUT_TOP_RIGHT:
    625                     gravity = Gravity.TOP | Gravity.RIGHT;
    626                     break;
    627                 case TvSettings.PIP_LAYOUT_BOTTOM_LEFT:
    628                     gravity = Gravity.BOTTOM | Gravity.LEFT;
    629                     break;
    630                 case TvSettings.PIP_LAYOUT_BOTTOM_RIGHT:
    631                     gravity = Gravity.BOTTOM | Gravity.RIGHT;
    632                     break;
    633                 default:
    634                     throw new IllegalArgumentException("Invalid PIP location: " + mPipLayout);
    635             }
    636         }
    637 
    638         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mPipView.getLayoutParams();
    639         if (lp.width != width || lp.height != height || lp.getMarginStart() != startMargin
    640                 || lp.getMarginEnd() != endMargin || lp.topMargin != topMargin
    641                 || lp.bottomMargin != bottomMargin || lp.gravity != gravity) {
    642             lp.width = width;
    643             lp.height = height;
    644             lp.setMarginStart(startMargin);
    645             lp.setMarginEnd(endMargin);
    646             lp.topMargin = topMargin;
    647             lp.bottomMargin = bottomMargin;
    648             lp.gravity = gravity;
    649             mPipView.setLayoutParams(lp);
    650         }
    651     }
    652 
    653     private void initTvAnimatorIfNeeded() {
    654         if (mTvViewAnimator != null) {
    655             return;
    656         }
    657 
    658         // TvViewAnimator animates TvView by repeatedly re-layouting TvView.
    659         // TvView includes a SurfaceView on which scale/translation effects do not work. Normally,
    660         // SurfaceView can be animated by changing left/top/right/bottom directly using
    661         // ObjectAnimator, although it would require calling getChildAt(0) against TvView (which is
    662         // supposed to be opaque). More importantly, this method does not work in case of TvView,
    663         // because TvView may request layout itself during animation and layout SurfaceView with
    664         // its own parameters when TvInputService requests to do so.
    665         mTvViewAnimator = new ObjectAnimator();
    666         mTvViewAnimator.setTarget(mTvView);
    667         mTvViewAnimator.setProperty(
    668                 Property.of(FrameLayout.class, ViewGroup.LayoutParams.class, "layoutParams"));
    669         mTvViewAnimator.setDuration(mResources.getInteger(R.integer.tvview_anim_duration));
    670         mTvViewAnimator.addListener(new AnimatorListenerAdapter() {
    671             private boolean mCanceled = false;
    672 
    673             @Override
    674             public void onAnimationCancel(Animator animation) {
    675                 mCanceled = true;
    676             }
    677 
    678             @Override
    679             public void onAnimationEnd(Animator animation) {
    680                 if (mCanceled) {
    681                     mCanceled = false;
    682                     return;
    683                 }
    684                 mHandler.post(new Runnable() {
    685                     @Override
    686                     public void run() {
    687                         setTvViewPosition(mTvViewLayoutParams, mTvViewFrame, false);
    688                     }
    689                 });
    690             }
    691         });
    692         mTvViewAnimator.addUpdateListener(new AnimatorUpdateListener() {
    693             @Override
    694             public void onAnimationUpdate(ValueAnimator animator) {
    695                 float fraction = animator.getAnimatedFraction();
    696                 mLastAnimatedTvViewFrame = new MarginLayoutParams(0, 0);
    697                 interpolateMarginsRelative(mLastAnimatedTvViewFrame,
    698                         mOldTvViewFrame, mTvViewFrame, fraction);
    699                 updatePipView(mLastAnimatedTvViewFrame);
    700             }
    701         });
    702     }
    703 
    704     private void initBackgroundAnimatorIfNeeded() {
    705         if (mBackgroundAnimator != null) {
    706             return;
    707         }
    708 
    709         mBackgroundAnimator = new ObjectAnimator();
    710         mBackgroundAnimator.setTarget(mContentView);
    711         mBackgroundAnimator.setPropertyName("backgroundColor");
    712         mBackgroundAnimator
    713                 .setDuration(mResources.getInteger(R.integer.tvactivity_background_anim_duration));
    714         mBackgroundAnimator.addListener(new AnimatorListenerAdapter() {
    715             @Override
    716             public void onAnimationEnd(Animator animation) {
    717                 mHandler.post(new Runnable() {
    718                     @Override
    719                     public void run() {
    720                         mContentView.setBackgroundColor(mBackgroundColor);
    721                     }
    722                 });
    723             }
    724         });
    725     }
    726 
    727     private void applyDisplayMode(float videoDisplayAspectRatio, boolean animate,
    728             boolean forceUpdate) {
    729         if (videoDisplayAspectRatio <= 0f) {
    730             videoDisplayAspectRatio = (float) mWindowWidth / mWindowHeight;
    731         }
    732         if (mAppliedDisplayedMode == mDisplayMode
    733                 && mAppliedTvViewStartMargin == mTvViewStartMargin
    734                 && mAppliedTvViewEndMargin == mTvViewEndMargin
    735                 && Math.abs(mAppliedVideoDisplayAspectRatio - videoDisplayAspectRatio) <
    736                         DISPLAY_ASPECT_RATIO_EPSILON) {
    737             if (!forceUpdate) {
    738                 return;
    739             }
    740         } else {
    741             mAppliedDisplayedMode = mDisplayMode;
    742             mAppliedTvViewStartMargin = mTvViewStartMargin;
    743             mAppliedTvViewEndMargin = mTvViewEndMargin;
    744             mAppliedVideoDisplayAspectRatio = videoDisplayAspectRatio;
    745         }
    746         int availableAreaWidth = mWindowWidth - mTvViewStartMargin - mTvViewEndMargin;
    747         int availableAreaHeight = availableAreaWidth * mWindowHeight / mWindowWidth;
    748         FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(0, 0,
    749                 ((FrameLayout.LayoutParams) mTvView.getLayoutParams()).gravity);
    750         int displayMode = mDisplayMode;
    751         double availableAreaRatio = 0;
    752         double videoRatio = 0;
    753         if (availableAreaWidth <= 0 || availableAreaHeight <= 0) {
    754             displayMode = DisplayMode.MODE_FULL;
    755             Log.w(TAG, "Some resolution info is missing during applyDisplayMode. ("
    756                     + "availableAreaWidth=" + availableAreaWidth + ", availableAreaHeight="
    757                     + availableAreaHeight + ")");
    758         } else {
    759             availableAreaRatio = (double) availableAreaWidth / availableAreaHeight;
    760             videoRatio = videoDisplayAspectRatio;
    761         }
    762 
    763         int tvViewFrameTop = (mWindowHeight - availableAreaHeight) / 2;
    764         MarginLayoutParams tvViewFrame = createMarginLayoutParams(
    765                 mTvViewStartMargin, mTvViewEndMargin, tvViewFrameTop, tvViewFrameTop);
    766         layoutParams.width = availableAreaWidth;
    767         layoutParams.height = availableAreaHeight;
    768         switch (displayMode) {
    769             case DisplayMode.MODE_FULL:
    770                 layoutParams.width = availableAreaWidth;
    771                 layoutParams.height = availableAreaHeight;
    772                 break;
    773             case DisplayMode.MODE_ZOOM:
    774                 if (videoRatio < availableAreaRatio) {
    775                     // Y axis will be clipped.
    776                     layoutParams.width = availableAreaWidth;
    777                     layoutParams.height = (int) Math.round(availableAreaWidth / videoRatio);
    778                 } else {
    779                     // X axis will be clipped.
    780                     layoutParams.width = (int) Math.round(availableAreaHeight * videoRatio);
    781                     layoutParams.height = availableAreaHeight;
    782                 }
    783                 break;
    784             case DisplayMode.MODE_NORMAL:
    785                 if (videoRatio < availableAreaRatio) {
    786                     // X axis has black area.
    787                     layoutParams.width = (int) Math.round(availableAreaHeight * videoRatio);
    788                     layoutParams.height = availableAreaHeight;
    789                 } else {
    790                     // Y axis has black area.
    791                     layoutParams.width = availableAreaWidth;
    792                     layoutParams.height = (int) Math.round(availableAreaWidth / videoRatio);
    793                 }
    794                 break;
    795         }
    796 
    797         // FrameLayout has an issue with centering when left and right margins differ.
    798         // So stick to Gravity.START | Gravity.CENTER_VERTICAL.
    799         int marginStart = mTvViewStartMargin + (availableAreaWidth - layoutParams.width) / 2;
    800         layoutParams.setMarginStart(marginStart);
    801         // Set marginEnd as well because setTvViewPosition uses both start/end margin.
    802         layoutParams.setMarginEnd(mWindowWidth - layoutParams.width - marginStart);
    803 
    804         setBackgroundColor(mResources.getColor(isTvViewFullScreen()
    805                 ? R.color.tvactivity_background : R.color.tvactivity_background_on_shrunken_tvview,
    806                         null), layoutParams, animate);
    807         setTvViewPosition(layoutParams, tvViewFrame, animate);
    808 
    809         // Update the current display mode.
    810         mTvOptionsManager.onDisplayModeChanged(displayMode);
    811     }
    812 
    813     private static int interpolate(int start, int end, float fraction) {
    814         return (int) (start + (end - start) * fraction);
    815     }
    816 
    817     private static void interpolateMarginsRelative(MarginLayoutParams out,
    818             MarginLayoutParams startValue, MarginLayoutParams endValue, float fraction) {
    819         out.topMargin = interpolate(startValue.topMargin, endValue.topMargin, fraction);
    820         out.bottomMargin = interpolate(startValue.bottomMargin, endValue.bottomMargin, fraction);
    821         out.setMarginStart(interpolate(startValue.getMarginStart(), endValue.getMarginStart(),
    822                 fraction));
    823         out.setMarginEnd(interpolate(startValue.getMarginEnd(), endValue.getMarginEnd(), fraction));
    824         out.width = interpolate(startValue.width, endValue.width, fraction);
    825         out.height = interpolate(startValue.height, endValue.height, fraction);
    826     }
    827 
    828     private MarginLayoutParams createMarginLayoutParams(
    829             int startMargin, int endMargin, int topMargin, int bottomMargin) {
    830         MarginLayoutParams lp = new MarginLayoutParams(0, 0);
    831         lp.setMarginStart(startMargin);
    832         lp.setMarginEnd(endMargin);
    833         lp.topMargin = topMargin;
    834         lp.bottomMargin = bottomMargin;
    835         lp.width = mWindowWidth - startMargin - endMargin;
    836         lp.height = mWindowHeight - topMargin - bottomMargin;
    837         return lp;
    838     }
    839 }
    840