1 // Copyright 2015 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.compositor.layouts.phone; 6 7 import android.animation.Animator; 8 import android.animation.AnimatorListenerAdapter; 9 import android.animation.AnimatorSet; 10 import android.content.Context; 11 import android.graphics.RectF; 12 13 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; 14 import org.chromium.chrome.browser.compositor.LayerTitleCache; 15 import org.chromium.chrome.browser.compositor.layouts.Layout; 16 import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost; 17 import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost; 18 import org.chromium.chrome.browser.compositor.layouts.components.LayoutTab; 19 import org.chromium.chrome.browser.compositor.layouts.content.TabContentManager; 20 import org.chromium.chrome.browser.compositor.layouts.eventfilter.BlackHoleEventFilter; 21 import org.chromium.chrome.browser.compositor.layouts.phone.stack.Stack; 22 import org.chromium.chrome.browser.compositor.scene_layer.TabListSceneLayer; 23 import org.chromium.chrome.browser.layouts.EventFilter; 24 import org.chromium.chrome.browser.layouts.LayoutType; 25 import org.chromium.chrome.browser.layouts.animation.CompositorAnimationHandler; 26 import org.chromium.chrome.browser.layouts.animation.CompositorAnimator; 27 import org.chromium.chrome.browser.layouts.scene_layer.SceneLayer; 28 import org.chromium.chrome.browser.tab.Tab; 29 import org.chromium.chrome.browser.tabmodel.TabModel; 30 import org.chromium.ui.interpolators.BakedBezierInterpolator; 31 import org.chromium.ui.resources.ResourceManager; 32 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.LinkedList; 37 38 /** 39 * This class handles animating the opening of new tabs. 40 */ 41 public class SimpleAnimationLayout extends Layout { 42 /** Animation for discarding a tab. */ 43 private CompositorAnimator mDiscardAnimator; 44 45 /** The animation for a tab being created in the foreground. */ 46 private AnimatorSet mTabCreatedForegroundAnimation; 47 48 /** The animation for a tab being created in the background. */ 49 private AnimatorSet mTabCreatedBackgroundAnimation; 50 51 /** Fraction to scale tabs by during animation. */ 52 public static final float SCALE_FRACTION = 0.90f; 53 54 /** Duration of the first step of the background animation: zooming out, rotating in */ 55 private static final long BACKGROUND_STEP1_DURATION = 300; 56 /** Duration of the second step of the background animation: pause */ 57 private static final long BACKGROUND_STEP2_DURATION = 150; 58 /** Duration of the third step of the background animation: zooming in, sliding out */ 59 private static final long BACKGROUND_STEP3_DURATION = 300; 60 /** Percentage of the screen covered by the new tab */ 61 private static final float BACKGROUND_COVER_PCTG = 0.5f; 62 63 /** The time duration of the animation */ 64 protected static final int FOREGROUND_ANIMATION_DURATION = 300; 65 66 /** The time duration of the animation */ 67 protected static final int TAB_CLOSED_ANIMATION_DURATION = 250; 68 69 /** 70 * A cached {@link LayoutTab} representation of the currently closing tab. If it's not 71 * null, it means tabClosing() has been called to start animation setup but 72 * tabClosed() has not yet been called to finish animation startup 73 */ 74 private LayoutTab mClosedTab; 75 76 private LayoutTab mAnimatedTab; 77 private final TabListSceneLayer mSceneLayer; 78 private final BlackHoleEventFilter mBlackHoleEventFilter; 79 80 /** 81 * Creates an instance of the {@link SimpleAnimationLayout}. 82 * @param context The current Android's context. 83 * @param updateHost The {@link LayoutUpdateHost} view for this layout. 84 * @param renderHost The {@link LayoutRenderHost} view for this layout. 85 */ SimpleAnimationLayout( Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost)86 public SimpleAnimationLayout( 87 Context context, LayoutUpdateHost updateHost, LayoutRenderHost renderHost) { 88 super(context, updateHost, renderHost); 89 mBlackHoleEventFilter = new BlackHoleEventFilter(context); 90 mSceneLayer = new TabListSceneLayer(); 91 } 92 93 @Override getViewportMode()94 public @ViewportMode int getViewportMode() { 95 return ViewportMode.USE_PREVIOUS_BROWSER_CONTROLS_STATE; 96 } 97 98 @Override show(long time, boolean animate)99 public void show(long time, boolean animate) { 100 super.show(time, animate); 101 102 if (mTabModelSelector != null && mTabContentManager != null) { 103 Tab tab = mTabModelSelector.getCurrentTab(); 104 if (tab != null && tab.isNativePage()) mTabContentManager.cacheTabThumbnail(tab); 105 } 106 107 reset(); 108 } 109 110 @Override handlesTabCreating()111 public boolean handlesTabCreating() { 112 return true; 113 } 114 115 @Override handlesTabClosing()116 public boolean handlesTabClosing() { 117 return true; 118 } 119 120 @Override updateLayout(long time, long dt)121 protected void updateLayout(long time, long dt) { 122 super.updateLayout(time, dt); 123 if (mLayoutTabs == null) return; 124 boolean needUpdate = false; 125 for (int i = mLayoutTabs.length - 1; i >= 0; i--) { 126 needUpdate = updateSnap(dt, mLayoutTabs[i]) || needUpdate; 127 } 128 if (needUpdate) requestUpdate(); 129 } 130 131 @Override onTabCreating(int sourceTabId)132 public void onTabCreating(int sourceTabId) { 133 super.onTabCreating(sourceTabId); 134 reset(); 135 136 // Make sure any currently running animations can't influence tab if we are reusing it. 137 forceAnimationToFinish(); 138 139 ensureSourceTabCreated(sourceTabId); 140 } 141 ensureSourceTabCreated(int sourceTabId)142 private void ensureSourceTabCreated(int sourceTabId) { 143 if (mLayoutTabs != null && mLayoutTabs.length == 1 144 && mLayoutTabs[0].getId() == sourceTabId) { 145 return; 146 } 147 // Just draw the source tab on the screen. 148 TabModel sourceModel = mTabModelSelector.getModelForTabId(sourceTabId); 149 if (sourceModel == null) return; 150 LayoutTab sourceLayoutTab = 151 createLayoutTab(sourceTabId, sourceModel.isIncognito(), NO_CLOSE_BUTTON, NO_TITLE); 152 sourceLayoutTab.setBorderAlpha(0.0f); 153 154 mLayoutTabs = new LayoutTab[] {sourceLayoutTab}; 155 updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(sourceTabId))); 156 } 157 158 @Override onTabCreated(long time, int id, int index, int sourceId, boolean newIsIncognito, boolean background, float originX, float originY)159 public void onTabCreated(long time, int id, int index, int sourceId, boolean newIsIncognito, 160 boolean background, float originX, float originY) { 161 super.onTabCreated(time, id, index, sourceId, newIsIncognito, background, originX, originY); 162 ensureSourceTabCreated(sourceId); 163 if (background && mLayoutTabs != null && mLayoutTabs.length > 0) { 164 tabCreatedInBackground(id, sourceId, newIsIncognito, originX, originY); 165 } else { 166 tabCreatedInForeground(id, sourceId, newIsIncognito, originX, originY); 167 } 168 } 169 170 /** 171 * Animate opening a tab in the foreground. 172 * 173 * @param id The id of the new tab to animate. 174 * @param sourceId The id of the tab that spawned this new tab. 175 * @param newIsIncognito true if the new tab is an incognito tab. 176 * @param originX The X coordinate of the last touch down event that spawned this tab. 177 * @param originY The Y coordinate of the last touch down event that spawned this tab. 178 */ tabCreatedInForeground( int id, int sourceId, boolean newIsIncognito, float originX, float originY)179 private void tabCreatedInForeground( 180 int id, int sourceId, boolean newIsIncognito, float originX, float originY) { 181 LayoutTab newLayoutTab = createLayoutTab(id, newIsIncognito, NO_CLOSE_BUTTON, NO_TITLE); 182 if (mLayoutTabs == null || mLayoutTabs.length == 0) { 183 mLayoutTabs = new LayoutTab[] {newLayoutTab}; 184 } else { 185 mLayoutTabs = new LayoutTab[] {mLayoutTabs[0], newLayoutTab}; 186 } 187 updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(id, sourceId))); 188 189 newLayoutTab.setBorderAlpha(0.0f); 190 newLayoutTab.setStaticToViewBlend(1.f); 191 192 forceAnimationToFinish(); 193 194 CompositorAnimationHandler handler = getAnimationHandler(); 195 CompositorAnimator scaleAnimation = CompositorAnimator.ofWritableFloatPropertyKey( 196 handler, newLayoutTab, LayoutTab.SCALE, 0f, 1f, FOREGROUND_ANIMATION_DURATION); 197 198 CompositorAnimator alphaAnimation = CompositorAnimator.ofWritableFloatPropertyKey( 199 handler, newLayoutTab, LayoutTab.ALPHA, 0f, 1f, FOREGROUND_ANIMATION_DURATION); 200 201 CompositorAnimator xAnimation = CompositorAnimator.ofWritableFloatPropertyKey( 202 handler, newLayoutTab, LayoutTab.X, originX, 0f, FOREGROUND_ANIMATION_DURATION); 203 CompositorAnimator yAnimation = CompositorAnimator.ofWritableFloatPropertyKey( 204 handler, newLayoutTab, LayoutTab.Y, originY, 0f, FOREGROUND_ANIMATION_DURATION); 205 206 mTabCreatedForegroundAnimation = new AnimatorSet(); 207 mTabCreatedForegroundAnimation.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 208 mTabCreatedForegroundAnimation.playTogether( 209 scaleAnimation, alphaAnimation, xAnimation, yAnimation); 210 mTabCreatedForegroundAnimation.start(); 211 212 mTabModelSelector.selectModel(newIsIncognito); 213 startHiding(id, false); 214 } 215 216 /** 217 * Animate opening a tab in the background. 218 * 219 * @param id The id of the new tab to animate. 220 * @param sourceId The id of the tab that spawned this new tab. 221 * @param newIsIncognito true if the new tab is an incognito tab. 222 * @param originX The X screen coordinate in dp of the last touch down event that spawned 223 * this tab. 224 * @param originY The Y screen coordinate in dp of the last touch down event that spawned 225 * this tab. 226 */ tabCreatedInBackground( int id, int sourceId, boolean newIsIncognito, float originX, float originY)227 private void tabCreatedInBackground( 228 int id, int sourceId, boolean newIsIncognito, float originX, float originY) { 229 LayoutTab newLayoutTab = createLayoutTab(id, newIsIncognito, NO_CLOSE_BUTTON, NEED_TITLE); 230 // mLayoutTabs should already have the source tab from tabCreating(). 231 assert mLayoutTabs.length == 1; 232 LayoutTab sourceLayoutTab = mLayoutTabs[0]; 233 mLayoutTabs = new LayoutTab[] {sourceLayoutTab, newLayoutTab}; 234 updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(id, sourceId))); 235 236 forceAnimationToFinish(); 237 238 newLayoutTab.setBorderAlpha(0.0f); 239 final float scale = SCALE_FRACTION; 240 final float margin = Math.min(getWidth(), getHeight()) * (1.0f - scale) / 2.0f; 241 242 CompositorAnimationHandler handler = getAnimationHandler(); 243 Collection<Animator> animationList = new ArrayList<>(5); 244 245 // Step 1: zoom out the source tab and bring in the new tab 246 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 247 handler, sourceLayoutTab, LayoutTab.SCALE, 1f, scale, BACKGROUND_STEP1_DURATION)); 248 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 249 handler, sourceLayoutTab, LayoutTab.X, 0f, margin, BACKGROUND_STEP1_DURATION)); 250 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 251 handler, sourceLayoutTab, LayoutTab.Y, 0f, margin, BACKGROUND_STEP1_DURATION)); 252 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 253 LayoutTab.BORDER_SCALE, 1f / scale, 1f, BACKGROUND_STEP1_DURATION)); 254 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 255 LayoutTab.BORDER_ALPHA, 0f, 1f, BACKGROUND_STEP1_DURATION)); 256 257 AnimatorSet step1Source = new AnimatorSet(); 258 step1Source.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 259 step1Source.playTogether(animationList); 260 261 float pauseX = margin; 262 float pauseY = margin; 263 if (getOrientation() == Orientation.PORTRAIT) { 264 pauseY = BACKGROUND_COVER_PCTG * getHeight(); 265 } else { 266 pauseX = BACKGROUND_COVER_PCTG * getWidth(); 267 } 268 269 animationList = new ArrayList<>(4); 270 271 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 272 handler, newLayoutTab, LayoutTab.ALPHA, 0f, 1f, BACKGROUND_STEP1_DURATION / 2)); 273 274 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 275 handler, newLayoutTab, LayoutTab.SCALE, 0f, scale, BACKGROUND_STEP1_DURATION)); 276 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 277 handler, newLayoutTab, LayoutTab.X, originX, pauseX, BACKGROUND_STEP1_DURATION)); 278 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 279 handler, newLayoutTab, LayoutTab.Y, originY, pauseY, BACKGROUND_STEP1_DURATION)); 280 281 AnimatorSet step1New = new AnimatorSet(); 282 step1New.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE); 283 step1New.playTogether(animationList); 284 285 AnimatorSet step1 = new AnimatorSet(); 286 step1.playTogether(step1New, step1Source); 287 288 // step 2: pause and admire the nice tabs 289 290 // step 3: zoom in the source tab and slide down the new tab 291 animationList = new ArrayList<>(7); 292 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 293 LayoutTab.SCALE, scale, 1f, BACKGROUND_STEP3_DURATION, 294 BakedBezierInterpolator.TRANSFORM_CURVE)); 295 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 296 LayoutTab.X, margin, 0f, BACKGROUND_STEP3_DURATION, 297 BakedBezierInterpolator.TRANSFORM_CURVE)); 298 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 299 LayoutTab.Y, margin, 0f, BACKGROUND_STEP3_DURATION, 300 BakedBezierInterpolator.TRANSFORM_CURVE)); 301 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 302 LayoutTab.BORDER_SCALE, 1f, 1f / scale, BACKGROUND_STEP3_DURATION, 303 BakedBezierInterpolator.TRANSFORM_CURVE)); 304 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, sourceLayoutTab, 305 LayoutTab.BORDER_ALPHA, 1f, 0f, BACKGROUND_STEP3_DURATION, 306 BakedBezierInterpolator.TRANSFORM_CURVE)); 307 308 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey( 309 handler, newLayoutTab, LayoutTab.ALPHA, 1f, 0f, BACKGROUND_STEP3_DURATION)); 310 311 if (getOrientation() == Orientation.PORTRAIT) { 312 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, newLayoutTab, 313 LayoutTab.Y, pauseY, getHeight(), BACKGROUND_STEP3_DURATION, 314 BakedBezierInterpolator.FADE_OUT_CURVE)); 315 } else { 316 animationList.add(CompositorAnimator.ofWritableFloatPropertyKey(handler, newLayoutTab, 317 LayoutTab.X, pauseX, getWidth(), BACKGROUND_STEP3_DURATION, 318 BakedBezierInterpolator.FADE_OUT_CURVE)); 319 } 320 321 AnimatorSet step3 = new AnimatorSet(); 322 step3.setStartDelay(BACKGROUND_STEP2_DURATION); 323 step3.addListener(new AnimatorListenerAdapter() { 324 @Override 325 public void onAnimationEnd(Animator animation) { 326 // Once the animation has finished, we can switch layouts. 327 startHiding(sourceId, false); 328 } 329 }); 330 step3.playTogether(animationList); 331 332 mTabCreatedBackgroundAnimation = new AnimatorSet(); 333 mTabCreatedBackgroundAnimation.playSequentially(step1, step3); 334 mTabCreatedBackgroundAnimation.start(); 335 336 mTabModelSelector.selectModel(newIsIncognito); 337 } 338 339 /** 340 * Set up for the tab closing animation 341 */ 342 @Override onTabClosing(long time, int id)343 public void onTabClosing(long time, int id) { 344 reset(); 345 346 // Make sure any currently running animations can't influence tab if we are reusing it. 347 forceAnimationToFinish(); 348 349 // Create the {@link LayoutTab} for the tab before it is destroyed. 350 TabModel model = mTabModelSelector.getModelForTabId(id); 351 if (model != null) { 352 mClosedTab = createLayoutTab(id, model.isIncognito(), NO_CLOSE_BUTTON, NO_TITLE); 353 mClosedTab.setBorderAlpha(0.0f); 354 mLayoutTabs = new LayoutTab[] {mClosedTab}; 355 updateCacheVisibleIds(new LinkedList<Integer>(Arrays.asList(id))); 356 } else { 357 mLayoutTabs = null; 358 mClosedTab = null; 359 } 360 // Only close the id at the end when we are done querying the model. 361 super.onTabClosing(time, id); 362 } 363 364 /** 365 * Animate the closing of a tab 366 */ 367 @Override onTabClosed(long time, int id, int nextId, boolean incognito)368 public void onTabClosed(long time, int id, int nextId, boolean incognito) { 369 super.onTabClosed(time, id, nextId, incognito); 370 371 if (mClosedTab != null) { 372 TabModel nextModel = mTabModelSelector.getModelForTabId(nextId); 373 if (nextModel != null) { 374 LayoutTab nextLayoutTab = 375 createLayoutTab(nextId, nextModel.isIncognito(), NO_CLOSE_BUTTON, NO_TITLE); 376 nextLayoutTab.setDrawDecoration(false); 377 378 mLayoutTabs = new LayoutTab[] {nextLayoutTab, mClosedTab}; 379 updateCacheVisibleIds( 380 new LinkedList<Integer>(Arrays.asList(nextId, mClosedTab.getId()))); 381 } else { 382 mLayoutTabs = new LayoutTab[] {mClosedTab}; 383 } 384 385 forceAnimationToFinish(); 386 mAnimatedTab = mClosedTab; 387 mDiscardAnimator = CompositorAnimator.ofFloat(getAnimationHandler(), 0, 388 getDiscardRange(), TAB_CLOSED_ANIMATION_DURATION, 389 (CompositorAnimator a) -> setDiscardAmount(a.getAnimatedValue())); 390 mDiscardAnimator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 391 mDiscardAnimator.start(); 392 393 mClosedTab = null; 394 if (nextModel != null) { 395 mTabModelSelector.selectModel(nextModel.isIncognito()); 396 } 397 } 398 startHiding(nextId, false); 399 } 400 401 /** 402 * Updates the position, scale, rotation and alpha values of mAnimatedTab. 403 * 404 * @param discard The value that specify how far along are we in the discard animation. 0 is 405 * filling the screen. Valid values are [-range .. range] where range is 406 * computed by {@link SimpleAnimationLayout#getDiscardRange()}. 407 */ setDiscardAmount(float discard)408 private void setDiscardAmount(float discard) { 409 if (mAnimatedTab != null) { 410 final float range = getDiscardRange(); 411 final float scale = Stack.computeDiscardScale(discard, range, true); 412 413 final float deltaX = mAnimatedTab.getOriginalContentWidth(); 414 final float deltaY = mAnimatedTab.getOriginalContentHeight() / 2.f; 415 mAnimatedTab.setX(deltaX * (1.f - scale)); 416 mAnimatedTab.setY(deltaY * (1.f - scale)); 417 mAnimatedTab.setScale(scale); 418 mAnimatedTab.setBorderScale(scale); 419 mAnimatedTab.setAlpha(Stack.computeDiscardAlpha(discard, range)); 420 } 421 } 422 423 /** 424 * @return The range of the discard amount. 425 */ getDiscardRange()426 private float getDiscardRange() { 427 return Math.min(getWidth(), getHeight()) * Stack.DISCARD_RANGE_SCREEN; 428 } 429 430 @Override forceAnimationToFinish()431 protected void forceAnimationToFinish() { 432 super.forceAnimationToFinish(); 433 if (mDiscardAnimator != null) mDiscardAnimator.end(); 434 if (mTabCreatedForegroundAnimation != null) mTabCreatedForegroundAnimation.end(); 435 if (mTabCreatedBackgroundAnimation != null) mTabCreatedBackgroundAnimation.end(); 436 } 437 438 /** 439 * Resets the internal state. 440 */ reset()441 private void reset() { 442 mLayoutTabs = null; 443 mAnimatedTab = null; 444 mClosedTab = null; 445 } 446 447 @Override getEventFilter()448 protected EventFilter getEventFilter() { 449 return mBlackHoleEventFilter; 450 } 451 452 @Override getSceneLayer()453 protected SceneLayer getSceneLayer() { 454 return mSceneLayer; 455 } 456 457 @Override updateSceneLayer(RectF viewport, RectF contentViewport, LayerTitleCache layerTitleCache, TabContentManager tabContentManager, ResourceManager resourceManager, BrowserControlsStateProvider browserControls)458 protected void updateSceneLayer(RectF viewport, RectF contentViewport, 459 LayerTitleCache layerTitleCache, TabContentManager tabContentManager, 460 ResourceManager resourceManager, BrowserControlsStateProvider browserControls) { 461 super.updateSceneLayer(viewport, contentViewport, layerTitleCache, tabContentManager, 462 resourceManager, browserControls); 463 assert mSceneLayer != null; 464 // The content viewport is intentionally sent as both params below. 465 mSceneLayer.pushLayers(getContext(), contentViewport, contentViewport, this, 466 layerTitleCache, tabContentManager, resourceManager, browserControls, 467 SceneLayer.INVALID_RESOURCE_ID, 0, 0); 468 } 469 470 @Override getLayoutType()471 public int getLayoutType() { 472 return LayoutType.SIMPLE_ANIMATION; 473 } 474 } 475