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 }