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.toolbar.top; 6 7 import android.animation.Animator; 8 import android.animation.AnimatorSet; 9 import android.animation.ObjectAnimator; 10 import android.animation.ValueAnimator; 11 import android.animation.ValueAnimator.AnimatorUpdateListener; 12 import android.annotation.SuppressLint; 13 import android.content.Context; 14 import android.content.res.ColorStateList; 15 import android.content.res.Resources; 16 import android.graphics.Canvas; 17 import android.graphics.Color; 18 import android.graphics.Point; 19 import android.graphics.PorterDuff; 20 import android.graphics.Rect; 21 import android.graphics.drawable.ColorDrawable; 22 import android.graphics.drawable.Drawable; 23 import android.os.Build; 24 import android.os.SystemClock; 25 import android.util.AttributeSet; 26 import android.util.Property; 27 import android.util.TypedValue; 28 import android.view.Gravity; 29 import android.view.MotionEvent; 30 import android.view.View; 31 import android.view.View.OnClickListener; 32 import android.view.ViewDebug; 33 import android.view.ViewGroup; 34 import android.view.ViewStub; 35 import android.view.ViewTreeObserver; 36 import android.widget.FrameLayout; 37 import android.widget.ImageButton; 38 import android.widget.ImageView; 39 import android.widget.TextView; 40 41 import androidx.annotation.IntDef; 42 import androidx.annotation.Nullable; 43 import androidx.annotation.VisibleForTesting; 44 import androidx.appcompat.graphics.drawable.DrawableWrapper; 45 import androidx.core.graphics.drawable.DrawableCompat; 46 47 import org.chromium.base.ApiCompatibilityUtils; 48 import org.chromium.base.MathUtils; 49 import org.chromium.base.TraceEvent; 50 import org.chromium.chrome.R; 51 import org.chromium.chrome.browser.device.DeviceClassManager; 52 import org.chromium.chrome.browser.feature_engagement.TrackerFactory; 53 import org.chromium.chrome.browser.omnibox.LocationBar; 54 import org.chromium.chrome.browser.omnibox.LocationBarCoordinator; 55 import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils; 56 import org.chromium.chrome.browser.partnercustomizations.PartnerBrowserCustomizations; 57 import org.chromium.chrome.browser.profiles.Profile; 58 import org.chromium.chrome.browser.tab.Tab; 59 import org.chromium.chrome.browser.tasks.ReturnToChromeExperimentsUtil; 60 import org.chromium.chrome.browser.toolbar.ButtonData; 61 import org.chromium.chrome.browser.toolbar.HomeButton; 62 import org.chromium.chrome.browser.toolbar.KeyboardNavigationListener; 63 import org.chromium.chrome.browser.toolbar.NewTabPageDelegate; 64 import org.chromium.chrome.browser.toolbar.TabCountProvider; 65 import org.chromium.chrome.browser.toolbar.TabCountProvider.TabCountObserver; 66 import org.chromium.chrome.browser.toolbar.TabSwitcherDrawable; 67 import org.chromium.chrome.browser.toolbar.ToolbarColors; 68 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator; 69 import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.UrlExpansionObserver; 70 import org.chromium.components.browser_ui.styles.ChromeColors; 71 import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener; 72 import org.chromium.components.browser_ui.widget.animation.Interpolators; 73 import org.chromium.components.embedder_support.util.UrlUtilities; 74 import org.chromium.components.feature_engagement.EventConstants; 75 import org.chromium.ui.base.LocalizationUtils; 76 import org.chromium.ui.base.ViewUtils; 77 import org.chromium.ui.interpolators.BakedBezierInterpolator; 78 import org.chromium.ui.util.ColorUtils; 79 80 import java.lang.annotation.Retention; 81 import java.lang.annotation.RetentionPolicy; 82 import java.util.ArrayList; 83 import java.util.List; 84 85 /** 86 * Phone specific toolbar implementation. 87 */ 88 public class ToolbarPhone extends ToolbarLayout implements OnClickListener, TabCountObserver { 89 /** The amount of time transitioning from one theme color to another should take in ms. */ 90 public static final long THEME_COLOR_TRANSITION_DURATION = 250; 91 92 public static final int URL_FOCUS_CHANGE_ANIMATION_DURATION_MS = 225; 93 private static final int URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS = 100; 94 private static final int URL_CLEAR_FOCUS_EXPERIMENTAL_BUTTON_DELAY_MS = 150; 95 private static final int URL_CLEAR_FOCUS_TABSTACK_DELAY_MS = 200; 96 private static final int URL_CLEAR_FOCUS_MENU_DELAY_MS = 250; 97 98 private static final int TAB_SWITCHER_MODE_EXIT_FADE_ANIMATION_DURATION_MS = 100; 99 private static final int TAB_SWITCHER_MODE_POST_EXIT_ANIMATION_DURATION_MS = 100; 100 101 // Values used during animation to show/hide optional toolbar button. 102 public static final int LOC_BAR_WIDTH_CHANGE_ANIMATION_DURATION_MS = 225; 103 private static final int EXPERIMENTAL_ICON_ANIMATION_DURATION_MS = 100; 104 private static final int EXPERIMENTAL_ICON_ANIMATION_DELAY_MS = 125; 105 106 private static final float UNINITIALIZED_FRACTION = -1f; 107 108 /** States that the toolbar can be in regarding the tab switcher. */ 109 protected static final int STATIC_TAB = 0; 110 protected static final int TAB_SWITCHER = 1; 111 protected static final int ENTERING_TAB_SWITCHER = 2; 112 protected static final int EXITING_TAB_SWITCHER = 3; 113 114 @ViewDebug.ExportedProperty(category = "chrome", mapping = { 115 @ViewDebug.IntToString(from = STATIC_TAB, to = "STATIC_TAB"), 116 @ViewDebug.IntToString(from = TAB_SWITCHER, to = "TAB_SWITCHER"), 117 @ViewDebug.IntToString(from = ENTERING_TAB_SWITCHER, to = "ENTERING_TAB_SWITCHER"), 118 @ViewDebug.IntToString(from = EXITING_TAB_SWITCHER, to = "EXITING_TAB_SWITCHER") 119 }) 120 121 static final int LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA = 51; 122 123 private TabCountProvider mTabCountProvider; 124 125 protected LocationBarCoordinator mLocationBar; 126 127 private ViewGroup mToolbarButtonsContainer; 128 protected @Nullable ToggleTabStackButton mToggleTabStackButton; 129 protected @Nullable HomeButton mHomeButton; 130 private TextView mUrlBar; 131 protected View mUrlActionContainer; 132 protected ImageView mToolbarShadow; 133 private @Nullable ImageButton mOptionalButton; 134 private boolean mOptionalButtonUsesTint; 135 136 private ObjectAnimator mTabSwitcherModeAnimation; 137 private ObjectAnimator mDelayedTabSwitcherModeAnimation; 138 139 @ViewDebug.ExportedProperty(category = "chrome") 140 protected int mTabSwitcherState; 141 142 // This determines whether or not the toolbar draws as expected (false) or whether it always 143 // draws as if it's showing the non-tabswitcher, non-animating toolbar. This is used in grabbing 144 // a bitmap to use as a texture representation of this view. 145 @ViewDebug.ExportedProperty(category = "chrome") 146 protected boolean mTextureCaptureMode; 147 private boolean mForceTextureCapture; 148 private boolean mLightDrawablesUsedForLastTextureCapture; 149 private int mTabCountForLastTextureCapture; 150 151 @ViewDebug.ExportedProperty(category = "chrome") 152 private boolean mAnimateNormalToolbar; 153 @ViewDebug.ExportedProperty(category = "chrome") 154 private boolean mDelayingTabSwitcherAnimation; 155 156 private TabSwitcherDrawable mTabSwitcherAnimationTabStackDrawable; 157 // Value that determines the amount of transition from the normal toolbar mode to TabSwitcher 158 // mode. 0 = entirely in normal mode and 1.0 = entirely in TabSwitcher mode. In between values 159 // can be used for animating between the two view modes. 160 @ViewDebug.ExportedProperty(category = "chrome") 161 protected float mTabSwitcherModeFraction; 162 163 // Used to clip the toolbar during the fade transition into and out of TabSwitcher mode. Only 164 // used when |mAnimateNormalToolbar| is false. 165 @ViewDebug.ExportedProperty(category = "chrome") 166 private Rect mClipRect; 167 168 @ViewDebug.ExportedProperty(category = "chrome") 169 protected boolean mUrlFocusChangeInProgress; 170 171 /** 1.0 is 100% focused, 0 is completely unfocused */ 172 @ViewDebug.ExportedProperty(category = "chrome") 173 private float mUrlFocusChangeFraction; 174 175 /** 176 * The degree to which the omnibox has expanded to full width, either because it is getting 177 * focused or the NTP search box is being scrolled up. Note that in the latter case, the actual 178 * width of the omnibox is not interpolated linearly from this value. The value will be the 179 * maximum of {@link #mUrlFocusChangeFraction} and {@link #mNtpSearchBoxScrollFraction}. 180 * 181 * 0.0 == no expansion, 1.0 == fully expanded. 182 */ 183 @ViewDebug.ExportedProperty(category = "chrome") 184 protected float mUrlExpansionFraction; 185 private AnimatorSet mUrlFocusLayoutAnimator; 186 187 protected boolean mDisableLocationBarRelayout; 188 protected boolean mLayoutLocationBarInFocusedMode; 189 private boolean mLayoutLocationBarWithoutExtraButton; 190 protected int mUnfocusedLocationBarLayoutWidth; 191 protected int mUnfocusedLocationBarLayoutLeft; 192 protected int mUnfocusedLocationBarLayoutRight; 193 private boolean mUnfocusedLocationBarUsesTransparentBg; 194 195 private int mLocationBarBackgroundAlpha = 255; 196 private float mNtpSearchBoxScrollFraction = UNINITIALIZED_FRACTION; 197 protected ColorDrawable mToolbarBackground; 198 199 /** The omnibox background (white with a shadow). */ 200 private Drawable mLocationBarBackground; 201 private Drawable mActiveLocationBarBackground; 202 203 protected boolean mForceDrawLocationBarBackground; 204 205 /** The boundaries of the omnibox, without the NTP-specific offset applied. */ 206 protected final Rect mLocationBarBackgroundBounds = new Rect(); 207 208 private final Rect mBackgroundOverlayBounds = new Rect(); 209 210 /** Offset applied to the bounds of the omnibox if we are showing a New Tab Page. */ 211 private final Rect mLocationBarBackgroundNtpOffset = new Rect(); 212 213 /** 214 * Offsets applied to the <i>contents</i> of the omnibox if we are showing a New Tab Page. 215 * This can be different from {@link #mLocationBarBackgroundNtpOffset} due to the fact that we 216 * extend the omnibox horizontally beyond the screen boundaries when focused, to hide its 217 * rounded corners. 218 */ 219 private float mLocationBarNtpOffsetLeft; 220 private float mLocationBarNtpOffsetRight; 221 222 private final Rect mNtpSearchBoxBounds = new Rect(); 223 protected final Point mNtpSearchBoxTranslation = new Point(); 224 225 protected final int mToolbarSidePadding; 226 227 private ValueAnimator mBrandColorTransitionAnimation; 228 private boolean mBrandColorTransitionActive; 229 230 private boolean mIsHomeButtonEnabled; 231 232 private Runnable mLayoutUpdater; 233 234 /** The vertical inset of the location bar background. */ 235 private int mLocationBarBackgroundVerticalInset; 236 237 /** The current color of the location bar. */ 238 private int mCurrentLocationBarColor; 239 240 /** Whether the toolbar has a pending request to call {@link triggerUrlFocusAnimation()}. */ 241 private boolean mPendingTriggerUrlFocusRequest; 242 243 /** 244 * Used to specify the visual state of the toolbar. 245 */ 246 @IntDef({VisualState.NORMAL, VisualState.INCOGNITO, VisualState.BRAND_COLOR, 247 VisualState.NEW_TAB_NORMAL}) 248 @Retention(RetentionPolicy.SOURCE) 249 private @interface VisualState { 250 int NORMAL = 0; 251 int INCOGNITO = 1; 252 int BRAND_COLOR = 2; 253 int NEW_TAB_NORMAL = 3; 254 } 255 256 protected @VisualState int mVisualState = VisualState.NORMAL; 257 258 private float mPreTextureCaptureAlpha = 1f; 259 private int mPreTextureCaptureVisibility; 260 private boolean mIsOverlayTabStackDrawableLight; 261 262 private AnimatorSet mOptionalButtonAnimator; 263 private boolean mOptionalButtonAnimationRunning; 264 private int mOptionalButtonTranslation; 265 private int mUrlFocusTranslationX; 266 267 /** 268 * The progress fraction for the location bar width change animation that is run when the 269 * optional button is shown/hidden. Animates from 1.f to 0.f when showing the button and 270 * 0.f to 1.f when hiding the button, where 0.f indicates the location bar width is not offset 271 * at all for the animation. 272 */ 273 private float mLocBarWidthChangeFraction; 274 275 /** 276 * A global layout listener used to capture a new texture when the experimental toolbar button 277 * is added or removed. 278 */ 279 private ViewTreeObserver.OnGlobalLayoutListener mOptionalButtonLayoutListener; 280 281 // The following are some properties used during animation. We use explicit property classes 282 // to avoid the cost of reflection for each animation setup. 283 284 private final Property<ToolbarPhone, Float> mUrlFocusChangeFractionProperty = 285 new Property<ToolbarPhone, Float>(Float.class, "") { 286 @Override 287 public Float get(ToolbarPhone object) { 288 return object.mUrlFocusChangeFraction; 289 } 290 291 @Override 292 public void set(ToolbarPhone object, Float value) { 293 setUrlFocusChangeFraction(value); 294 } 295 }; 296 297 private final Property<ToolbarPhone, Float> mTabSwitcherModeFractionProperty = 298 new Property<ToolbarPhone, Float>(Float.class, "") { 299 @Override 300 public Float get(ToolbarPhone object) { 301 return object.mTabSwitcherModeFraction; 302 } 303 304 @Override 305 public void set(ToolbarPhone object, Float value) { 306 object.mTabSwitcherModeFraction = value; 307 triggerPaintInvalidate(ToolbarPhone.this::postInvalidateOnAnimation); 308 } 309 }; 310 311 private final Property<ToolbarPhone, Float> mLocBarWidthChangeFractionProperty = 312 new Property<ToolbarPhone, Float>(Float.class, "") { 313 @Override 314 public Float get(ToolbarPhone object) { 315 return object.mLocBarWidthChangeFraction; 316 } 317 318 @Override 319 public void set(ToolbarPhone object, Float value) { 320 mLocBarWidthChangeFraction = value; 321 updateLocationBarLayoutForExpansionAnimation(); 322 } 323 }; 324 325 /** 326 * Constructs a ToolbarPhone object. 327 * 328 * @param context The Context in which this View object is created. 329 * @param attrs The AttributeSet that was specified with this View. 330 */ ToolbarPhone(Context context, AttributeSet attrs)331 public ToolbarPhone(Context context, AttributeSet attrs) { 332 super(context, attrs); 333 mToolbarSidePadding = getResources().getDimensionPixelOffset(R.dimen.toolbar_edge_padding); 334 } 335 336 @Override onFinishInflate()337 public void onFinishInflate() { 338 try (TraceEvent te = TraceEvent.scoped("ToolbarPhone.onFinishInflate")) { 339 super.onFinishInflate(); 340 mToolbarButtonsContainer = (ViewGroup) findViewById(R.id.toolbar_buttons); 341 mHomeButton = findViewById(R.id.home_button); 342 mUrlBar = (TextView) findViewById(R.id.url_bar); 343 mUrlActionContainer = findViewById(R.id.url_action_container); 344 mToolbarBackground = 345 new ColorDrawable(getToolbarColorForVisualState(VisualState.NORMAL)); 346 347 setLayoutTransition(null); 348 349 if (getMenuButtonCoordinator() != null) { 350 getMenuButtonCoordinator().setVisibility(true); 351 } 352 353 inflateTabSwitchingResources(); 354 355 setWillNotDraw(false); 356 mUrlFocusTranslationX = 357 getResources().getDimensionPixelSize(R.dimen.toolbar_url_focus_translation_x); 358 } 359 } 360 361 @Override setLocationBarCoordinator(LocationBarCoordinator locationBarCoordinator)362 public void setLocationBarCoordinator(LocationBarCoordinator locationBarCoordinator) { 363 mLocationBar = locationBarCoordinator; 364 initLocationBarBackground(); 365 } 366 367 @Override destroy()368 void destroy() { 369 cancelAnimations(); 370 super.destroy(); 371 } 372 373 /** 374 * Initializes the background, padding, margins, etc. for the location bar background. 375 */ initLocationBarBackground()376 private void initLocationBarBackground() { 377 Resources res = getResources(); 378 mLocationBarBackgroundVerticalInset = 379 res.getDimensionPixelSize(R.dimen.location_bar_vertical_margin); 380 mLocationBarBackground = createModernLocationBarBackground(getResources()); 381 382 int lateralPadding = res.getDimensionPixelOffset(R.dimen.location_bar_lateral_padding); 383 mLocationBar.getPhoneCoordinator().setPadding(lateralPadding, 0, lateralPadding, 0); 384 385 mActiveLocationBarBackground = mLocationBarBackground; 386 } 387 388 /** 389 * @return The drawable for the modern location bar background. 390 */ createModernLocationBarBackground(Resources resources)391 public static Drawable createModernLocationBarBackground(Resources resources) { 392 Drawable drawable = ApiCompatibilityUtils.getDrawable( 393 resources, R.drawable.modern_toolbar_text_box_background_with_primary_color); 394 drawable.mutate(); 395 drawable.setColorFilter( 396 ApiCompatibilityUtils.getColor(resources, R.color.toolbar_text_box_background), 397 PorterDuff.Mode.SRC_IN); 398 return drawable; 399 } 400 401 /** 402 * Set the background color of the location bar to appropriately match the theme color. 403 */ updateModernLocationBarColor(int color)404 private void updateModernLocationBarColor(int color) { 405 if (mCurrentLocationBarColor == color) return; 406 mCurrentLocationBarColor = color; 407 mLocationBarBackground.setColorFilter(color, PorterDuff.Mode.SRC_IN); 408 } 409 410 /** 411 * Get the corresponding location bar color for a toolbar color. 412 * @param toolbarColor The color of the toolbar. 413 * @return The location bar color. 414 */ getLocationBarColorForToolbarColor(int toolbarColor)415 private int getLocationBarColorForToolbarColor(int toolbarColor) { 416 return ToolbarColors.getTextBoxColorForToolbarBackgroundInNonNativePage( 417 getResources(), toolbarColor, isIncognito()); 418 } 419 inflateTabSwitchingResources()420 private void inflateTabSwitchingResources() { 421 mToggleTabStackButton = findViewById(R.id.tab_switcher_button); 422 mToggleTabStackButton.setClickable(false); 423 } 424 enableTabSwitchingResources()425 private void enableTabSwitchingResources() { 426 mToggleTabStackButton.setOnKeyListener(new KeyboardNavigationListener() { 427 @Override 428 public View getNextFocusForward() { 429 if (getMenuButtonCoordinator().isShown()) { 430 return getMenuButtonCoordinator().getMenuButton(); 431 } else { 432 return getCurrentTabView(); 433 } 434 } 435 436 @Override 437 public View getNextFocusBackward() { 438 return findViewById(R.id.url_bar); 439 } 440 }); 441 } 442 443 /** 444 * Sets up click and key listeners once we have native library available to handle clicks. 445 */ 446 @Override onNativeLibraryReady()447 protected void onNativeLibraryReady() { 448 super.onNativeLibraryReady(); 449 450 enableTabSwitchingResources(); 451 452 if (mHomeButton != null) { 453 mHomeButton.setOnClickListener(this); 454 } 455 456 getMenuButtonCoordinator().setOnKeyListener(new KeyboardNavigationListener() { 457 @Override 458 public View getNextFocusForward() { 459 return getCurrentTabView(); 460 } 461 462 @Override 463 public View getNextFocusBackward() { 464 return mToggleTabStackButton; 465 } 466 467 @Override 468 protected boolean handleEnterKeyPress() { 469 return getMenuButtonCoordinator().onEnterKeyPress(); 470 } 471 }); 472 473 /** 474 * Calls the {@link triggerUrlFocusAnimation()} here if it is skipped in {@link 475 * handleOmniboxInOverviewMode()}. See https://crbug.com/1152306. Keyboard shouldn't be 476 * shown here. 477 */ 478 if (mPendingTriggerUrlFocusRequest) { 479 mPendingTriggerUrlFocusRequest = false; 480 triggerUrlFocusAnimation( 481 getToolbarDataProvider().isInOverviewAndShowingOmnibox() && !urlHasFocus(), 482 /* shouldShowKeyboard= */ false); 483 } 484 485 updateVisualsForLocationBarState(); 486 } 487 488 @Override onInterceptTouchEvent(MotionEvent ev)489 public boolean onInterceptTouchEvent(MotionEvent ev) { 490 // If the NTP is partially scrolled, prevent all touch events to the child views. This 491 // is to not allow a secondary touch event to trigger entering the tab switcher, which 492 // can lead to really odd snapshots and transitions to the switcher. 493 if (mNtpSearchBoxScrollFraction != 0f && mNtpSearchBoxScrollFraction != 1f 494 && mNtpSearchBoxScrollFraction != UNINITIALIZED_FRACTION) { 495 return true; 496 } 497 498 return super.onInterceptTouchEvent(ev); 499 } 500 501 @Override onTouchEvent(MotionEvent ev)502 public boolean onTouchEvent(MotionEvent ev) { 503 // Forward touch events to the NTP if the toolbar is moved away but the search box hasn't 504 // reached the top of the page yet. 505 if (mNtpSearchBoxTranslation.y < 0 506 && mLocationBar.getPhoneCoordinator().getTranslationY() > 0) { 507 return getToolbarDataProvider().getNewTabPageDelegate().dispatchTouchEvent(ev); 508 } 509 510 return super.onTouchEvent(ev); 511 } 512 513 @Override onClick(View v)514 public void onClick(View v) { 515 // Don't allow clicks while the omnibox is being focused. 516 if (mLocationBar != null && mLocationBar.getPhoneCoordinator().hasFocus()) { 517 return; 518 } 519 if (mHomeButton != null && mHomeButton == v) { 520 openHomepage(); 521 if (isNativeLibraryReady() 522 && PartnerBrowserCustomizations.getInstance() 523 .isHomepageProviderAvailableAndEnabled()) { 524 Profile profile = getToolbarDataProvider().getProfile(); 525 TrackerFactory.getTrackerForProfile(profile).notifyEvent( 526 EventConstants.PARTNER_HOME_PAGE_BUTTON_PRESSED); 527 } 528 } 529 } 530 531 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)532 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 533 if (!mDisableLocationBarRelayout) { 534 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 535 536 boolean changed = layoutLocationBar(MeasureSpec.getSize(widthMeasureSpec)); 537 if (!isInTabSwitcherMode()) updateUrlExpansionAnimation(); 538 if (!changed) return; 539 } else { 540 updateUnfocusedLocationBarLayoutParams(); 541 } 542 543 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 544 } 545 546 /** 547 * @return True if layout bar's unfocused width has changed, potentially causing updates to 548 * visual elements. If this happens during measurement pass, then toolbar's layout needs 549 * to be remeasured. 550 */ updateUnfocusedLocationBarLayoutParams()551 private boolean updateUnfocusedLocationBarLayoutParams() { 552 int leftViewBounds = getViewBoundsLeftOfLocationBar(mVisualState); 553 int rightViewBounds = getViewBoundsRightOfLocationBar(mVisualState); 554 555 mUnfocusedLocationBarLayoutLeft = leftViewBounds; 556 mUnfocusedLocationBarLayoutRight = rightViewBounds; 557 int unfocusedLocationBarLayoutWidth = rightViewBounds - leftViewBounds; 558 if (mUnfocusedLocationBarLayoutWidth != unfocusedLocationBarLayoutWidth) { 559 mUnfocusedLocationBarLayoutWidth = unfocusedLocationBarLayoutWidth; 560 mLocationBar.setUnfocusedWidth(mUnfocusedLocationBarLayoutWidth); 561 return true; 562 } 563 return false; 564 } 565 566 /** 567 * @return The background drawable for the toolbar view. 568 */ 569 @VisibleForTesting getBackgroundDrawable()570 public ColorDrawable getBackgroundDrawable() { 571 return mToolbarBackground; 572 } 573 574 @SuppressLint("RtlHardcoded") layoutLocationBar(int containerWidth)575 private boolean layoutLocationBar(int containerWidth) { 576 TraceEvent.begin("ToolbarPhone.layoutLocationBar"); 577 // Note that Toolbar's direction depends on system layout direction while 578 // LocationBar's direction depends on its text inside. 579 FrameLayout.LayoutParams locationBarLayoutParams = 580 getFrameLayoutParams(getLocationBar().getContainerView()); 581 582 // Chrome prevents layout_gravity="left" from being defined in XML, but it simplifies 583 // the logic, so it is manually specified here. 584 locationBarLayoutParams.gravity = Gravity.TOP | Gravity.LEFT; 585 586 int width = 0; 587 int leftMargin = 0; 588 589 boolean changed = false; 590 591 // Always update the unfocused layout params regardless of whether we are using 592 // those in this current layout pass as they are needed for animations. 593 changed |= updateUnfocusedLocationBarLayoutParams(); 594 595 if (mLayoutLocationBarInFocusedMode 596 || (mVisualState == VisualState.NEW_TAB_NORMAL 597 && mTabSwitcherState == STATIC_TAB)) { 598 int priorVisibleWidth = 599 mLocationBar.getPhoneCoordinator().getOffsetOfFirstVisibleFocusedView(); 600 width = getFocusedLocationBarWidth(containerWidth, priorVisibleWidth); 601 leftMargin = getFocusedLocationBarLeftMargin(priorVisibleWidth); 602 } else { 603 width = mUnfocusedLocationBarLayoutWidth; 604 leftMargin = mUnfocusedLocationBarLayoutLeft; 605 } 606 607 if (mLayoutLocationBarWithoutExtraButton) { 608 float offset = getLocationBarWidthOffsetForOptionalButton(); 609 if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) leftMargin -= (int) offset; 610 width += (int) offset; 611 } 612 613 changed |= (width != locationBarLayoutParams.width); 614 locationBarLayoutParams.width = width; 615 616 changed |= (leftMargin != locationBarLayoutParams.leftMargin); 617 locationBarLayoutParams.leftMargin = leftMargin; 618 619 if (changed) updateLocationBarLayoutForExpansionAnimation(); 620 621 TraceEvent.end("ToolbarPhone.layoutLocationBar"); 622 return changed; 623 } 624 625 /** 626 * @param containerWidth The width of the view containing the location bar. 627 * @param priorVisibleWidth The width of any visible views prior to the location bar. 628 * @return The width of the location bar when it has focus. 629 */ getFocusedLocationBarWidth(int containerWidth, int priorVisibleWidth)630 private int getFocusedLocationBarWidth(int containerWidth, int priorVisibleWidth) { 631 int width = containerWidth - (2 * mToolbarSidePadding) + priorVisibleWidth; 632 633 return width; 634 } 635 636 /** 637 * @param priorVisibleWidth The width of any visible views prior to the location bar. 638 * @return The left margin of the location bar when it has focus. 639 */ getFocusedLocationBarLeftMargin(int priorVisibleWidth)640 private int getFocusedLocationBarLeftMargin(int priorVisibleWidth) { 641 int baseMargin = mToolbarSidePadding; 642 if (mLocationBar.getPhoneCoordinator().getLayoutDirection() == LAYOUT_DIRECTION_RTL) { 643 return baseMargin; 644 } else { 645 return baseMargin - priorVisibleWidth; 646 } 647 } 648 649 /** 650 * @param visualState The current {@link VisualState} of the toolbar. 651 * @return The left bounds of the location bar, accounting for any buttons on the left side 652 * of the toolbar. 653 */ getViewBoundsLeftOfLocationBar(@isualState int visualState)654 private int getViewBoundsLeftOfLocationBar(@VisualState int visualState) { 655 // Uses getMeasuredWidth()s instead of getLeft() because this is called in onMeasure 656 // and the layout values have not yet been set. 657 if (visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB) { 658 return mToolbarSidePadding; 659 } else if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { 660 return getBoundsAfterAccountingForRightButtons(); 661 } else { 662 return getBoundsAfterAccountingForLeftButton(); 663 } 664 } 665 666 /** 667 * @return The left bounds of the location bar after accounting for any visible left buttons. 668 */ getBoundsAfterAccountingForLeftButton()669 private int getBoundsAfterAccountingForLeftButton() { 670 int padding = mToolbarSidePadding; 671 if (mHomeButton != null && mHomeButton.getVisibility() != GONE) { 672 padding = mHomeButton.getMeasuredWidth(); 673 } 674 return padding; 675 } 676 677 /** 678 * @param visualState The current {@link VisualState} of the toolbar. 679 * @return The right bounds of the location bar, accounting for any buttons on the right side 680 * of the toolbar. 681 */ getViewBoundsRightOfLocationBar(@isualState int visualState)682 private int getViewBoundsRightOfLocationBar(@VisualState int visualState) { 683 // Uses getMeasuredWidth()s instead of getRight() because this is called in onMeasure 684 // and the layout values have not yet been set. 685 if (visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB) { 686 return getMeasuredWidth() - mToolbarSidePadding; 687 } else if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) { 688 return getMeasuredWidth() - getBoundsAfterAccountingForLeftButton(); 689 } else { 690 return getMeasuredWidth() - getBoundsAfterAccountingForRightButtons(); 691 } 692 } 693 694 /** 695 * @return The right bounds of the location bar after accounting for any visible left buttons. 696 */ getBoundsAfterAccountingForRightButtons()697 private int getBoundsAfterAccountingForRightButtons() { 698 return Math.max(mToolbarSidePadding, mToolbarButtonsContainer.getMeasuredWidth()); 699 } 700 updateToolbarBackground(int color)701 private void updateToolbarBackground(int color) { 702 if (mToolbarBackground.getColor() == color) return; 703 mToolbarBackground.setColor(color); 704 invalidate(); 705 } 706 updateToolbarBackgroundFromState(@isualState int visualState)707 private void updateToolbarBackgroundFromState(@VisualState int visualState) { 708 updateToolbarBackground(getToolbarColorForVisualState(visualState)); 709 } 710 711 getToolbarColorForVisualState(final @VisualState int visualState)712 private int getToolbarColorForVisualState(final @VisualState int visualState) { 713 Resources res = getResources(); 714 switch (visualState) { 715 case VisualState.NEW_TAB_NORMAL: 716 // When the NTP fake search box is visible, the background color should be 717 // transparent. When the location bar reaches the top of the screen (i.e. location 718 // bar is fully expanded), the background needs to change back to the default 719 // toolbar color so that the NTP content is not visible beneath the toolbar. In 720 // between the transition, we set a translucent default toolbar color based on 721 // the expansion progress of the toolbar. 722 return androidx.core.graphics.ColorUtils.setAlphaComponent( 723 ChromeColors.getDefaultThemeColor(getResources(), false), 724 Math.round(mUrlExpansionFraction * 255)); 725 case VisualState.NORMAL: 726 return ChromeColors.getDefaultThemeColor(getResources(), false); 727 case VisualState.INCOGNITO: 728 return ChromeColors.getDefaultThemeColor(getResources(), true); 729 case VisualState.BRAND_COLOR: 730 return getToolbarDataProvider().getPrimaryColor(); 731 default: 732 assert false; 733 return ApiCompatibilityUtils.getColor(res, R.color.toolbar_background_primary); 734 } 735 } 736 737 @Override dispatchDraw(Canvas canvas)738 protected void dispatchDraw(Canvas canvas) { 739 if (!mTextureCaptureMode && mToolbarBackground.getColor() != Color.TRANSPARENT) { 740 // Update to compensate for orientation changes. 741 mToolbarBackground.setBounds(0, 0, getWidth(), getHeight()); 742 mToolbarBackground.draw(canvas); 743 } 744 745 if (mLocationBarBackground != null 746 && (mLocationBar.getPhoneCoordinator().getVisibility() == VISIBLE 747 || mTextureCaptureMode)) { 748 updateLocationBarBackgroundBounds(mLocationBarBackgroundBounds, mVisualState); 749 } 750 751 if (mTextureCaptureMode) { 752 drawTabSwitcherAnimationOverlay(canvas, 0.f); 753 } else { 754 boolean tabSwitcherAnimationFinished = false; 755 if (mTabSwitcherModeAnimation != null) { 756 tabSwitcherAnimationFinished = !mTabSwitcherModeAnimation.isRunning(); 757 758 // Perform the fade logic before super.dispatchDraw(canvas) so that we can properly 759 // set the values before the draw happens. 760 if (!mAnimateNormalToolbar) { 761 drawTabSwitcherFadeAnimation( 762 tabSwitcherAnimationFinished, mTabSwitcherModeFraction); 763 } 764 } 765 766 super.dispatchDraw(canvas); 767 768 if (mTabSwitcherModeAnimation != null) { 769 // Perform the overlay logic after super.dispatchDraw(canvas) as we need to draw on 770 // top of the current views. 771 if (mAnimateNormalToolbar) { 772 drawTabSwitcherAnimationOverlay(canvas, mTabSwitcherModeFraction); 773 } 774 775 // Clear the animation. 776 if (tabSwitcherAnimationFinished) mTabSwitcherModeAnimation = null; 777 } 778 } 779 } 780 781 @Override verifyDrawable(Drawable who)782 protected boolean verifyDrawable(Drawable who) { 783 return super.verifyDrawable(who) || who == mActiveLocationBarBackground; 784 } 785 onNtpScrollChanged(float scrollFraction)786 private void onNtpScrollChanged(float scrollFraction) { 787 mNtpSearchBoxScrollFraction = scrollFraction; 788 updateUrlExpansionFraction(); 789 updateUrlExpansionAnimation(); 790 } 791 792 /** 793 * @return True if the toolbar is showing tab switcher assets, including during transitions. 794 */ isInTabSwitcherMode()795 public boolean isInTabSwitcherMode() { 796 return mTabSwitcherState != STATIC_TAB; 797 } 798 799 /** 800 * Calculate the bounds for the location bar background and set them to {@code out}. 801 */ updateLocationBarBackgroundBounds(Rect out, @VisualState int visualState)802 private void updateLocationBarBackgroundBounds(Rect out, @VisualState int visualState) { 803 // Calculate the visible boundaries of the left and right most child views of the 804 // location bar. 805 float expansion = getExpansionFractionForVisualState(visualState); 806 int leftViewPosition = getLeftPositionOfLocationBarBackground(visualState); 807 int rightViewPosition = getRightPositionOfLocationBarBackground(visualState); 808 809 // The bounds are set by the following: 810 // - The left most visible location bar child view. 811 // - The top of the viewport is aligned with the top of the location bar. 812 // - The right most visible location bar child view. 813 // - The bottom of the viewport is aligned with the bottom of the location bar. 814 // Additional padding can be applied for use during animations. 815 out.set(leftViewPosition, 816 mLocationBar.getPhoneCoordinator().getTop() + mLocationBarBackgroundVerticalInset, 817 rightViewPosition, 818 mLocationBar.getPhoneCoordinator().getBottom() 819 - mLocationBarBackgroundVerticalInset); 820 } 821 822 /** 823 * @param visualState The current {@link VisualState} of the toolbar. 824 * @return The left drawing position for the location bar background. 825 */ getLeftPositionOfLocationBarBackground(@isualState int visualState)826 private int getLeftPositionOfLocationBarBackground(@VisualState int visualState) { 827 float expansion = getExpansionFractionForVisualState(visualState); 828 int leftViewPosition = 829 (int) MathUtils.interpolate(getViewBoundsLeftOfLocationBar(visualState), 830 getFocusedLeftPositionOfLocationBarBackground(), expansion); 831 832 if (mOptionalButtonAnimationRunning && getLayoutDirection() == LAYOUT_DIRECTION_RTL) { 833 leftViewPosition -= getLocationBarBackgroundOffsetForOptionalButton(); 834 } 835 836 return leftViewPosition; 837 } 838 839 /** 840 * @return The left drawing position for the location bar background when the location bar 841 * has focus. 842 */ getFocusedLeftPositionOfLocationBarBackground()843 private int getFocusedLeftPositionOfLocationBarBackground() { 844 return mToolbarSidePadding; 845 } 846 847 /** 848 * @param visualState The current {@link VisualState} of the toolbar. 849 * @return The right drawing position for the location bar background. 850 */ getRightPositionOfLocationBarBackground(@isualState int visualState)851 private int getRightPositionOfLocationBarBackground(@VisualState int visualState) { 852 float expansion = getExpansionFractionForVisualState(visualState); 853 int rightViewPosition = 854 (int) MathUtils.interpolate(getViewBoundsRightOfLocationBar(visualState), 855 getFocusedRightPositionOfLocationBarBackground(), expansion); 856 857 if (mOptionalButtonAnimationRunning && !(getLayoutDirection() == LAYOUT_DIRECTION_RTL)) { 858 rightViewPosition += getLocationBarBackgroundOffsetForOptionalButton(); 859 } 860 861 return rightViewPosition; 862 } 863 864 /** 865 * @return The location bar background position offset, for use when the optional button 866 * show/hide animation is running. 867 */ getLocationBarBackgroundOffsetForOptionalButton()868 private int getLocationBarBackgroundOffsetForOptionalButton() { 869 return (int) (getLocationBarWidthOffsetForOptionalButton() * mLocBarWidthChangeFraction); 870 } 871 872 /** 873 * @return The difference in the location bar width when the optional button is hidden 874 * rather than showing. This is effectively the width of the optional button with 875 * some adjustment to account for possible padding differences when the button 876 * visibility changes. 877 */ getLocationBarWidthOffsetForOptionalButton()878 private float getLocationBarWidthOffsetForOptionalButton() { 879 float widthChange = mOptionalButton.getWidth(); 880 881 // When the optional button is the only visible button after the location bar and the 882 // button is hidden mToolbarSidePadding is used for the padding after the location bar. 883 if (!isMenuButtonPresent()) { 884 widthChange -= mToolbarSidePadding; 885 } 886 return widthChange; 887 } 888 889 /** 890 * @return The right drawing position for the location bar background when the location bar 891 * has focus. 892 */ getFocusedRightPositionOfLocationBarBackground()893 private int getFocusedRightPositionOfLocationBarBackground() { 894 return getWidth() - mToolbarSidePadding; 895 } 896 getExpansionFractionForVisualState(@isualState int visualState)897 private float getExpansionFractionForVisualState(@VisualState int visualState) { 898 return visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB 899 || getToolbarDataProvider().isInOverviewAndShowingOmnibox() 900 ? 1 901 : mUrlExpansionFraction; 902 } 903 904 /** 905 * Updates progress of current the URL focus change animation. 906 * 907 * @param fraction 1.0 is 100% focused, 0 is completely unfocused. 908 */ setUrlFocusChangeFraction(float fraction)909 private void setUrlFocusChangeFraction(float fraction) { 910 mUrlFocusChangeFraction = fraction; 911 updateUrlExpansionFraction(); 912 updateUrlExpansionAnimation(); 913 } 914 updateUrlExpansionFraction()915 private void updateUrlExpansionFraction() { 916 mUrlExpansionFraction = Math.max(mNtpSearchBoxScrollFraction, mUrlFocusChangeFraction); 917 for (UrlExpansionObserver observer : mUrlExpansionObservers) { 918 observer.onUrlExpansionProgressChanged(mUrlExpansionFraction); 919 } 920 assert mUrlExpansionFraction >= 0; 921 assert mUrlExpansionFraction <= 1; 922 } 923 924 /** 925 * Updates the parameters relating to expanding the location bar, as the result of either a 926 * focus change or scrolling the New Tab Page. 927 */ updateUrlExpansionAnimation()928 private void updateUrlExpansionAnimation() { 929 // TODO(https://crbug.com/865801): Prevent url expansion signals from happening while the 930 // toolbar is not visible (e.g. in tab switcher mode). 931 if (isInTabSwitcherMode()) return; 932 933 int toolbarButtonVisibility = getToolbarButtonVisibility(); 934 mToolbarButtonsContainer.setVisibility(toolbarButtonVisibility); 935 if (!getToolbarDataProvider().isInOverviewAndShowingOmnibox()) { 936 if (mHomeButton != null && mHomeButton.getVisibility() != GONE) { 937 mHomeButton.setVisibility(toolbarButtonVisibility); 938 } 939 } 940 941 updateLocationBarLayoutForExpansionAnimation(); 942 } 943 944 /** 945 * @return The visibility for {@link #mToolbarButtonsContainer}. 946 */ getToolbarButtonVisibility()947 private int getToolbarButtonVisibility() { 948 return mUrlExpansionFraction == 1f ? INVISIBLE : VISIBLE; 949 } 950 951 /** 952 * Updates the location bar layout, as the result of either a focus change or scrolling the 953 * New Tab Page. 954 */ updateLocationBarLayoutForExpansionAnimation()955 private void updateLocationBarLayoutForExpansionAnimation() { 956 TraceEvent.begin("ToolbarPhone.updateLocationBarLayoutForExpansionAnimation"); 957 FrameLayout.LayoutParams locationBarLayoutParams = 958 mLocationBar.getPhoneCoordinator().getFrameLayoutParams(); 959 int currentLeftMargin = locationBarLayoutParams.leftMargin; 960 int currentWidth = locationBarLayoutParams.width; 961 962 float locationBarBaseTranslationX = mUnfocusedLocationBarLayoutLeft - currentLeftMargin; 963 if (mOptionalButtonAnimationRunning) { 964 // When showing the button, we disable location bar relayout 965 // (mDisableLocationBarRelayout), so the location bar's left margin and 966 // mUnfocusedLocationBarLayoutLeft have not been updated to take into account the 967 // appearance of the optional icon. The views to left of the location bar will 968 // be wider than mUnfocusedlocationBarLayoutLeft in RTL, so adjust the translation by 969 // that amount. 970 // When hiding the button, we force a relayout without the optional toolbar button 971 // (mLayoutLocationBarWithoutExtraButton). mUnfocusedLocationBarLayoutLeft reflects 972 // the view bounds left of the location bar, which still includes the optional 973 // button. The location bar left margin, however, has been adjusted to reflect its 974 // end value when the optional button is fully hidden. The 975 // locationBarBaseTranslationX above accounts for the difference between 976 // mUnfocusedLocationBarLayoutLeft and the location bar's current left margin. 977 locationBarBaseTranslationX += 978 getViewBoundsLeftOfLocationBar(mVisualState) - mUnfocusedLocationBarLayoutLeft; 979 } 980 981 // When the dse icon is visible, the LocationBar needs additional translation to compensate 982 // for the dse icon being laid out when focused. This also affects the UrlBar, which is 983 // handled below. See comments in LocationBar#getLocationBarOffsetForFocusAnimation() for 984 // implementation details. 985 boolean isIncognito = getToolbarDataProvider().isIncognito(); 986 if (SearchEngineLogoUtils.shouldShowSearchEngineLogo(isIncognito)) { 987 locationBarBaseTranslationX += 988 mLocationBar.getPhoneCoordinator().getLocationBarOffsetForFocusAnimation( 989 hasFocus()); 990 } 991 992 boolean isLocationBarRtl = 993 mLocationBar.getPhoneCoordinator().getLayoutDirection() == LAYOUT_DIRECTION_RTL; 994 if (isLocationBarRtl) { 995 locationBarBaseTranslationX += mUnfocusedLocationBarLayoutWidth - currentWidth; 996 } 997 998 locationBarBaseTranslationX *= 1f 999 - (mOptionalButtonAnimationRunning ? mLocBarWidthChangeFraction 1000 : mUrlExpansionFraction); 1001 1002 mLocationBarBackgroundNtpOffset.setEmpty(); 1003 mLocationBarNtpOffsetLeft = 0; 1004 mLocationBarNtpOffsetRight = 0; 1005 1006 Tab currentTab = getToolbarDataProvider().getTab(); 1007 if (currentTab != null) { 1008 getToolbarDataProvider().getNewTabPageDelegate().setUrlFocusChangeAnimationPercent( 1009 mUrlFocusChangeFraction); 1010 if (isLocationBarShownInNTP() 1011 && !getToolbarDataProvider().isInOverviewAndShowingOmnibox()) { 1012 updateNtpTransitionAnimation(); 1013 } else { 1014 // Reset these values in case we transitioned to a different page during the 1015 // transition. 1016 resetNtpAnimationValues(); 1017 } 1018 } 1019 1020 float locationBarTranslationX; 1021 if (isLocationBarRtl) { 1022 locationBarTranslationX = locationBarBaseTranslationX + mLocationBarNtpOffsetRight; 1023 } else { 1024 locationBarTranslationX = locationBarBaseTranslationX + mLocationBarNtpOffsetLeft; 1025 } 1026 1027 mLocationBar.getPhoneCoordinator().setTranslationX(locationBarTranslationX); 1028 1029 // When the dse icon is enabled, the UrlBar needs additional translation to compensate for 1030 // the additional translation applied to the LocationBar. See comments in 1031 // LocationBar#getUrlBarTranslationXForToolbarAnimation() for implementation details. 1032 if (SearchEngineLogoUtils.shouldShowSearchEngineLogo(isIncognito)) { 1033 mUrlBar.setTranslationX( 1034 mLocationBar.getPhoneCoordinator().getUrlBarTranslationXForToolbarAnimation( 1035 mUrlExpansionFraction, hasFocus())); 1036 } else if (SearchEngineLogoUtils.isSearchEngineLogoEnabled()) { 1037 mUrlBar.setTranslationX(0); 1038 } 1039 1040 if (!mOptionalButtonAnimationRunning) { 1041 mUrlActionContainer.setTranslationX(getUrlActionsTranslationXForExpansionAnimation( 1042 isLocationBarRtl, locationBarBaseTranslationX)); 1043 mLocationBar.setUrlFocusChangeFraction(mUrlExpansionFraction); 1044 1045 // Only transition theme colors if in static tab mode that is not the NTP. In practice 1046 // this only runs when you focus the omnibox on a web page. 1047 if (!isLocationBarShownInNTP() && mTabSwitcherState == STATIC_TAB) { 1048 int defaultColor = ChromeColors.getDefaultThemeColor(getResources(), isIncognito()); 1049 int defaultLocationBarColor = getLocationBarColorForToolbarColor(defaultColor); 1050 int primaryColor = getToolbarDataProvider().getPrimaryColor(); 1051 int themedLocationBarColor = getLocationBarColorForToolbarColor(primaryColor); 1052 1053 updateToolbarBackground(ColorUtils.getColorWithOverlay( 1054 primaryColor, defaultColor, mUrlFocusChangeFraction)); 1055 1056 updateModernLocationBarColor(ColorUtils.getColorWithOverlay( 1057 themedLocationBarColor, defaultLocationBarColor, mUrlFocusChangeFraction)); 1058 } 1059 } 1060 1061 // Force an invalidation of the location bar to properly handle the clipping of the URL 1062 // bar text as a result of the URL action container translations. 1063 mLocationBar.getPhoneCoordinator().invalidate(); 1064 invalidate(); 1065 TraceEvent.end("ToolbarPhone.updateLocationBarLayoutForExpansionAnimation"); 1066 } 1067 1068 /** 1069 * Calculates the translation X for the URL actions container for use in the URL expansion 1070 * animation. 1071 * 1072 * @param isLocationBarRtl Whether the location bar layout is RTL. 1073 * @param locationBarBaseTranslationX The base location bar translation for the URL expansion 1074 * animation. 1075 * @return The translation X for the URL actions container. 1076 */ getUrlActionsTranslationXForExpansionAnimation( boolean isLocationBarRtl, float locationBarBaseTranslationX)1077 private float getUrlActionsTranslationXForExpansionAnimation( 1078 boolean isLocationBarRtl, float locationBarBaseTranslationX) { 1079 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 1080 float urlActionsTranslationX = 0; 1081 if (!isLocationBarRtl || isRtl) { 1082 // Negate the location bar translation to keep the URL action container in the same 1083 // place during the focus expansion. 1084 urlActionsTranslationX = -locationBarBaseTranslationX; 1085 } 1086 1087 if (isRtl) { 1088 urlActionsTranslationX += mLocationBarNtpOffsetLeft - mLocationBarNtpOffsetRight; 1089 } else { 1090 urlActionsTranslationX += mLocationBarNtpOffsetRight - mLocationBarNtpOffsetLeft; 1091 } 1092 1093 return urlActionsTranslationX; 1094 } 1095 1096 /** 1097 * Reset the parameters for the New Tab Page transition animation (expanding the location bar as 1098 * a result of scrolling the New Tab Page) to their default values. 1099 */ resetNtpAnimationValues()1100 private void resetNtpAnimationValues() { 1101 mLocationBarBackgroundNtpOffset.setEmpty(); 1102 mActiveLocationBarBackground = mLocationBarBackground; 1103 mNtpSearchBoxTranslation.set(0, 0); 1104 mLocationBar.getPhoneCoordinator().setTranslationY(0); 1105 if (!mUrlFocusChangeInProgress) { 1106 mToolbarButtonsContainer.setTranslationY(0); 1107 if (mHomeButton != null) mHomeButton.setTranslationY(0); 1108 } 1109 1110 if (!mUrlFocusChangeInProgress) { 1111 mToolbarShadow.setAlpha(mUrlBar.hasFocus() ? 0.f : 1.f); 1112 } 1113 1114 mLocationBar.getPhoneCoordinator().setAlpha(1); 1115 mForceDrawLocationBarBackground = false; 1116 mLocationBarBackgroundAlpha = 255; 1117 if (isIncognito() 1118 || (mUnfocusedLocationBarUsesTransparentBg && !mUrlFocusChangeInProgress 1119 && !mLocationBar.getPhoneCoordinator().hasFocus())) { 1120 mLocationBarBackgroundAlpha = LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA; 1121 } 1122 1123 setAncestorsShouldClipChildren(true); 1124 mNtpSearchBoxScrollFraction = UNINITIALIZED_FRACTION; 1125 updateUrlExpansionFraction(); 1126 } 1127 1128 /** 1129 * Updates the parameters of the New Tab Page transition animation (expanding the location bar 1130 * as a result of scrolling the New Tab Page). 1131 */ updateNtpTransitionAnimation()1132 private void updateNtpTransitionAnimation() { 1133 // Skip if in or entering tab switcher mode. 1134 if (mTabSwitcherState == TAB_SWITCHER || mTabSwitcherState == ENTERING_TAB_SWITCHER) return; 1135 1136 boolean isExpanded = mUrlExpansionFraction > 0f; 1137 setAncestorsShouldClipChildren(!isExpanded); 1138 if (!mUrlFocusChangeInProgress) { 1139 float alpha = 0.f; 1140 if (!mUrlBar.hasFocus() && mNtpSearchBoxScrollFraction == 1.f) { 1141 alpha = 1.f; 1142 } 1143 mToolbarShadow.setAlpha(alpha); 1144 } 1145 1146 NewTabPageDelegate ntpDelegate = getToolbarDataProvider().getNewTabPageDelegate(); 1147 ntpDelegate.getSearchBoxBounds(mNtpSearchBoxBounds, mNtpSearchBoxTranslation); 1148 int locationBarTranslationY = Math.max( 1149 0, (mNtpSearchBoxBounds.top - mLocationBar.getPhoneCoordinator().getTop())); 1150 mLocationBar.getPhoneCoordinator().setTranslationY(locationBarTranslationY); 1151 1152 updateButtonsTranslationY(); 1153 1154 // Linearly interpolate between the bounds of the search box on the NTP and the omnibox 1155 // background bounds. |shrinkage| is the scaling factor for the offset -- if it's 1, we are 1156 // shrinking the omnibox down to the size of the search box. 1157 float shrinkage = 1f 1158 - Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR.getInterpolation( 1159 mUrlExpansionFraction); 1160 1161 int leftBoundDifference = mNtpSearchBoxBounds.left - mLocationBarBackgroundBounds.left; 1162 int rightBoundDifference = mNtpSearchBoxBounds.right - mLocationBarBackgroundBounds.right; 1163 int verticalInset = (int) (getResources().getDimensionPixelSize( 1164 R.dimen.ntp_search_box_bounds_vertical_inset_modern) 1165 * (1.f - mUrlExpansionFraction)); 1166 mLocationBarBackgroundNtpOffset.set(Math.round(leftBoundDifference * shrinkage), 1167 locationBarTranslationY, Math.round(rightBoundDifference * shrinkage), 1168 locationBarTranslationY); 1169 mLocationBarBackgroundNtpOffset.inset(0, verticalInset); 1170 1171 mLocationBarNtpOffsetLeft = leftBoundDifference * shrinkage; 1172 mLocationBarNtpOffsetRight = rightBoundDifference * shrinkage; 1173 1174 mLocationBarBackgroundAlpha = isExpanded ? 255 : 0; 1175 mForceDrawLocationBarBackground = mLocationBarBackgroundAlpha > 0; 1176 float relativeAlpha = mLocationBarBackgroundAlpha / 255f; 1177 mLocationBar.getPhoneCoordinator().setAlpha(relativeAlpha); 1178 1179 // The search box on the NTP is visible if our omnibox is invisible, and vice-versa. 1180 ntpDelegate.setSearchBoxAlpha(1f - relativeAlpha); 1181 if (!mForceDrawLocationBarBackground) { 1182 if (mActiveLocationBarBackground instanceof NtpSearchBoxDrawable) { 1183 ((NtpSearchBoxDrawable) mActiveLocationBarBackground).resetBoundsToLastNonToolbar(); 1184 } 1185 } 1186 1187 updateToolbarBackgroundFromState(mVisualState); 1188 } 1189 1190 /** 1191 * Update the y translation of the buttons to make it appear as if they were scrolling with 1192 * the new tab page. 1193 */ updateButtonsTranslationY()1194 private void updateButtonsTranslationY() { 1195 int transY = mTabSwitcherState == STATIC_TAB ? Math.min(mNtpSearchBoxTranslation.y, 0) : 0; 1196 1197 mToolbarButtonsContainer.setTranslationY(transY); 1198 if (mHomeButton != null) mHomeButton.setTranslationY(transY); 1199 } 1200 setAncestorsShouldClipChildren(boolean clip)1201 private void setAncestorsShouldClipChildren(boolean clip) { 1202 if (!isLocationBarShownInNTP()) return; 1203 1204 ViewUtils.setAncestorsShouldClipChildren(this, clip); 1205 } 1206 drawTabSwitcherFadeAnimation(boolean animationFinished, float progress)1207 private void drawTabSwitcherFadeAnimation(boolean animationFinished, float progress) { 1208 setAlpha(progress); 1209 if (animationFinished) { 1210 mClipRect = null; 1211 } else if (mClipRect == null) { 1212 mClipRect = new Rect(); 1213 } 1214 if (mClipRect != null) mClipRect.set(0, 0, getWidth(), (int) (getHeight() * progress)); 1215 } 1216 1217 /** 1218 * When entering and exiting the TabSwitcher mode, we fade out or fade in the browsing 1219 * mode of the toolbar on top of the TabSwitcher mode version of it. We do this by 1220 * drawing all of the browsing mode views on top of the android view. 1221 */ 1222 @VisibleForTesting drawTabSwitcherAnimationOverlay(Canvas canvas, float animationProgress)1223 void drawTabSwitcherAnimationOverlay(Canvas canvas, float animationProgress) { 1224 if (!isNativeLibraryReady()) return; 1225 1226 float floatAlpha = 1 - animationProgress; 1227 int rgbAlpha = (int) (255 * floatAlpha); 1228 canvas.save(); 1229 canvas.translate(0, -animationProgress * mBackgroundOverlayBounds.height()); 1230 canvas.clipRect(mBackgroundOverlayBounds); 1231 1232 float previousAlpha = 0.f; 1233 if (mHomeButton != null && mHomeButton.getVisibility() != View.GONE) { 1234 // Draw the New Tab button used in the URL view. 1235 previousAlpha = mHomeButton.getAlpha(); 1236 mHomeButton.setAlpha(previousAlpha * floatAlpha); 1237 drawChild(canvas, mHomeButton, SystemClock.uptimeMillis()); 1238 mHomeButton.setAlpha(previousAlpha); 1239 } 1240 1241 // Draw the location/URL bar. 1242 previousAlpha = mLocationBar.getPhoneCoordinator().getAlpha(); 1243 mLocationBar.getPhoneCoordinator().setAlpha(previousAlpha * floatAlpha); 1244 // If the location bar is now fully transparent, do not bother drawing it. 1245 if (mLocationBar.getPhoneCoordinator().getAlpha() != 0 && isLocationBarCurrentlyShown()) { 1246 drawLocationBar(canvas, SystemClock.uptimeMillis()); 1247 } 1248 mLocationBar.getPhoneCoordinator().setAlpha(previousAlpha); 1249 1250 // Translate to draw end toolbar buttons. 1251 ViewUtils.translateCanvasToView(this, mToolbarButtonsContainer, canvas); 1252 1253 // Draw the optional button if necessary. 1254 if (mOptionalButton != null && mOptionalButton.getVisibility() != View.GONE) { 1255 canvas.save(); 1256 Drawable optionalButtonDrawable = mOptionalButton.getDrawable(); 1257 1258 ViewUtils.translateCanvasToView(mToolbarButtonsContainer, mOptionalButton, canvas); 1259 1260 int backgroundWidth = mOptionalButton.getDrawable().getIntrinsicWidth(); 1261 int backgroundHeight = mOptionalButton.getDrawable().getIntrinsicHeight(); 1262 int backgroundLeft = (mOptionalButton.getWidth() - mOptionalButton.getPaddingLeft() 1263 - mOptionalButton.getPaddingRight() - backgroundWidth) 1264 / 2; 1265 backgroundLeft += mOptionalButton.getPaddingLeft(); 1266 int backgroundTop = (mOptionalButton.getHeight() - mOptionalButton.getPaddingTop() 1267 - mOptionalButton.getPaddingBottom() - backgroundHeight) 1268 / 2; 1269 backgroundTop += mOptionalButton.getPaddingTop(); 1270 canvas.translate(backgroundLeft, backgroundTop); 1271 1272 optionalButtonDrawable.setAlpha(rgbAlpha); 1273 optionalButtonDrawable.draw(canvas); 1274 1275 canvas.restore(); 1276 } 1277 1278 // Draw the tab stack button and associated text if necessary. 1279 if (mTabSwitcherAnimationTabStackDrawable != null && mToggleTabStackButton != null 1280 && mUrlExpansionFraction != 1f) { 1281 // Draw the tab stack button image. 1282 canvas.save(); 1283 ViewUtils.translateCanvasToView( 1284 mToolbarButtonsContainer, mToggleTabStackButton, canvas); 1285 1286 int backgroundWidth = mToggleTabStackButton.getDrawable().getIntrinsicWidth(); 1287 int backgroundHeight = mToggleTabStackButton.getDrawable().getIntrinsicHeight(); 1288 int backgroundLeft = 1289 (mToggleTabStackButton.getWidth() - mToggleTabStackButton.getPaddingLeft() 1290 - mToggleTabStackButton.getPaddingRight() - backgroundWidth) 1291 / 2; 1292 backgroundLeft += mToggleTabStackButton.getPaddingLeft(); 1293 int backgroundTop = 1294 (mToggleTabStackButton.getHeight() - mToggleTabStackButton.getPaddingTop() 1295 - mToggleTabStackButton.getPaddingBottom() - backgroundHeight) 1296 / 2; 1297 backgroundTop += mToggleTabStackButton.getPaddingTop(); 1298 canvas.translate(backgroundLeft, backgroundTop); 1299 1300 mTabSwitcherAnimationTabStackDrawable.setBounds( 1301 mToggleTabStackButton.getDrawable().getBounds()); 1302 mTabSwitcherAnimationTabStackDrawable.setAlpha(rgbAlpha); 1303 mTabSwitcherAnimationTabStackDrawable.draw(canvas); 1304 canvas.restore(); 1305 } 1306 1307 // Draw the menu button if necessary. 1308 final MenuButtonCoordinator menuButtonCoordinator = getMenuButtonCoordinator(); 1309 if (menuButtonCoordinator != null) { 1310 menuButtonCoordinator.drawTabSwitcherAnimationOverlay( 1311 mToolbarButtonsContainer, canvas, rgbAlpha); 1312 } 1313 1314 mLightDrawablesUsedForLastTextureCapture = useLight(); 1315 1316 if (mTabSwitcherAnimationTabStackDrawable != null && mToggleTabStackButton != null) { 1317 mTabCountForLastTextureCapture = mTabSwitcherAnimationTabStackDrawable.getTabCount(); 1318 } 1319 1320 canvas.restore(); 1321 } 1322 1323 @Override drawChild(Canvas canvas, View child, long drawingTime)1324 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 1325 if (mLocationBar != null 1326 && child == mLocationBar.getPhoneCoordinator().getViewForDrawing()) { 1327 return drawLocationBar(canvas, drawingTime); 1328 } 1329 boolean clipped = false; 1330 1331 if (mLocationBarBackground != null) { 1332 canvas.save(); 1333 1334 int translationY = (int) mLocationBar.getPhoneCoordinator().getTranslationY(); 1335 int clipTop = mLocationBarBackgroundBounds.top + translationY; 1336 if (mUrlExpansionFraction != 0f && clipTop < child.getBottom()) { 1337 // For other child views, use the inverse clipping of the URL viewport. 1338 // Only necessary during animations. 1339 // Hardware mode does not support unioned clip regions, so clip using the 1340 // appropriate bounds based on whether the child is to the left or right of the 1341 // location bar. 1342 boolean isLeft = isChildLeft(child); 1343 1344 int clipBottom = mLocationBarBackgroundBounds.bottom + translationY; 1345 boolean verticalClip = false; 1346 if (translationY > 0f) { 1347 clipTop = child.getTop(); 1348 clipBottom = clipTop; 1349 verticalClip = true; 1350 } 1351 1352 if (isLeft) { 1353 int clipRight = verticalClip ? child.getMeasuredWidth() 1354 : mLocationBarBackgroundBounds.left; 1355 canvas.clipRect(0, clipTop, clipRight, clipBottom); 1356 } else { 1357 int clipLeft = verticalClip ? 0 : mLocationBarBackgroundBounds.right; 1358 canvas.clipRect(clipLeft, clipTop, getMeasuredWidth(), clipBottom); 1359 } 1360 } 1361 clipped = true; 1362 } 1363 boolean retVal = super.drawChild(canvas, child, drawingTime); 1364 if (clipped) canvas.restore(); 1365 return retVal; 1366 } 1367 isChildLeft(View child)1368 private boolean isChildLeft(View child) { 1369 return (mHomeButton != null && child == mHomeButton) ^ LocalizationUtils.isLayoutRtl(); 1370 } 1371 1372 /** 1373 * @return Whether or not the location bar should be drawing at any particular state of the 1374 * toolbar. 1375 */ shouldDrawLocationBar()1376 private boolean shouldDrawLocationBar() { 1377 return mLocationBarBackground != null 1378 && (mTabSwitcherState == STATIC_TAB || mTextureCaptureMode); 1379 } 1380 drawLocationBar(Canvas canvas, long drawingTime)1381 private boolean drawLocationBar(Canvas canvas, long drawingTime) { 1382 TraceEvent.begin("ToolbarPhone.drawLocationBar"); 1383 boolean clipped = false; 1384 if (shouldDrawLocationBar()) { 1385 canvas.save(); 1386 1387 if (shouldDrawLocationBarBackground()) { 1388 if (mActiveLocationBarBackground instanceof NtpSearchBoxDrawable) { 1389 ((NtpSearchBoxDrawable) mActiveLocationBarBackground) 1390 .markPendingBoundsUpdateFromToolbar(); 1391 } 1392 mActiveLocationBarBackground.setBounds( 1393 mLocationBarBackgroundBounds.left + mLocationBarBackgroundNtpOffset.left, 1394 mLocationBarBackgroundBounds.top + mLocationBarBackgroundNtpOffset.top, 1395 mLocationBarBackgroundBounds.right + mLocationBarBackgroundNtpOffset.right, 1396 mLocationBarBackgroundBounds.bottom 1397 + mLocationBarBackgroundNtpOffset.bottom); 1398 mActiveLocationBarBackground.draw(canvas); 1399 } 1400 1401 float locationBarClipLeft = 1402 mLocationBarBackgroundBounds.left + mLocationBarBackgroundNtpOffset.left; 1403 float locationBarClipRight = 1404 mLocationBarBackgroundBounds.right + mLocationBarBackgroundNtpOffset.right; 1405 float locationBarClipTop = 1406 mLocationBarBackgroundBounds.top + mLocationBarBackgroundNtpOffset.top; 1407 float locationBarClipBottom = 1408 mLocationBarBackgroundBounds.bottom + mLocationBarBackgroundNtpOffset.bottom; 1409 1410 final int locationBarPaddingStart = 1411 mLocationBar.getPhoneCoordinator().getPaddingStart(); 1412 final int locationBarPaddingEnd = mLocationBar.getPhoneCoordinator().getPaddingEnd(); 1413 final int locationBarDirection = 1414 mLocationBar.getPhoneCoordinator().getLayoutDirection(); 1415 // When unexpanded, the location bar's visible content boundaries are inset from the 1416 // viewport used to draw the background. During expansion transitions, compensation 1417 // is applied to increase the clip regions such that when the location bar converts 1418 // to the narrower collapsed layout the visible content is the same. 1419 if (mUrlExpansionFraction != 1f && !mOptionalButtonAnimationRunning) { 1420 int leftDelta = mUnfocusedLocationBarLayoutLeft 1421 - getViewBoundsLeftOfLocationBar(mVisualState); 1422 int rightDelta = getViewBoundsRightOfLocationBar(mVisualState) 1423 - mUnfocusedLocationBarLayoutLeft - mUnfocusedLocationBarLayoutWidth; 1424 float remainingFraction = 1f - mUrlExpansionFraction; 1425 1426 locationBarClipLeft += leftDelta * remainingFraction; 1427 locationBarClipRight -= rightDelta * remainingFraction; 1428 1429 // When the defocus animation is running, the location bar padding needs to be 1430 // subtracted from the clip bounds so that the location bar text width in the last 1431 // frame of the animation matches the text width of the unfocused location bar. 1432 if (locationBarDirection == LAYOUT_DIRECTION_RTL) { 1433 locationBarClipLeft += locationBarPaddingStart * remainingFraction; 1434 } else { 1435 locationBarClipRight -= locationBarPaddingEnd * remainingFraction; 1436 } 1437 } 1438 if (mOptionalButtonAnimationRunning) { 1439 if (locationBarDirection == LAYOUT_DIRECTION_RTL) { 1440 locationBarClipLeft += locationBarPaddingStart; 1441 } else { 1442 locationBarClipRight -= locationBarPaddingEnd; 1443 } 1444 } 1445 1446 // Offset the clip rect by a set amount to ensure the Google G is completely inside the 1447 // omnibox background when animating in. 1448 if (SearchEngineLogoUtils.shouldShowSearchEngineLogo(isIncognito()) 1449 && isLocationBarShownInNTP() && urlHasFocus() && mUrlFocusChangeInProgress) { 1450 if (locationBarDirection == LAYOUT_DIRECTION_RTL) { 1451 locationBarClipRight -= locationBarPaddingStart; 1452 } else { 1453 locationBarClipLeft += locationBarPaddingStart; 1454 } 1455 } 1456 1457 // Clip the location bar child to the URL viewport calculated in onDraw. 1458 canvas.clipRect(locationBarClipLeft, locationBarClipTop, locationBarClipRight, 1459 locationBarClipBottom); 1460 clipped = true; 1461 } 1462 1463 // TODO(1133482): Hide this View interaction if possible. 1464 boolean retVal = super.drawChild( 1465 canvas, mLocationBar.getPhoneCoordinator().getViewForDrawing(), drawingTime); 1466 1467 if (clipped) canvas.restore(); 1468 TraceEvent.end("ToolbarPhone.drawLocationBar"); 1469 return retVal; 1470 } 1471 1472 /** 1473 * @return Whether the location bar background should be drawn in 1474 * {@link #drawLocationBar(Canvas, long)}. 1475 */ shouldDrawLocationBarBackground()1476 private boolean shouldDrawLocationBarBackground() { 1477 return (mLocationBar.getPhoneCoordinator().getAlpha() > 0 1478 || mForceDrawLocationBarBackground) 1479 && !mTextureCaptureMode; 1480 } 1481 1482 @Override onSizeChanged(int w, int h, int oldw, int oldh)1483 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1484 mBackgroundOverlayBounds.set(0, 0, w, h); 1485 super.onSizeChanged(w, h, oldw, oldh); 1486 } 1487 1488 @Override onAttachedToWindow()1489 protected void onAttachedToWindow() { 1490 super.onAttachedToWindow(); 1491 1492 mToolbarShadow = (ImageView) getRootView().findViewById(R.id.toolbar_shadow); 1493 1494 // This is a workaround for http://crbug.com/574928. Since Jelly Bean is the lowest version 1495 // we support now and the next deprecation target, we decided to simply workaround. 1496 if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { 1497 mToolbarShadow.setImageDrawable(ApiCompatibilityUtils.getDrawable( 1498 getResources(), R.drawable.modern_toolbar_shadow)); 1499 } 1500 } 1501 1502 @Override draw(Canvas canvas)1503 public void draw(Canvas canvas) { 1504 // If capturing a texture of the toolbar, ensure the alpha is set prior to draw(...) being 1505 // called. The alpha is being used prior to getting to draw(...), so updating the value 1506 // after this point was having no affect. 1507 if (mTextureCaptureMode) assert getAlpha() == 1f; 1508 1509 // mClipRect can change in the draw call, so cache this value to ensure the canvas is 1510 // restored correctly. 1511 boolean shouldClip = !mTextureCaptureMode && mClipRect != null; 1512 if (shouldClip) { 1513 canvas.save(); 1514 canvas.clipRect(mClipRect); 1515 } 1516 super.draw(canvas); 1517 if (shouldClip) { 1518 canvas.restore(); 1519 1520 // Post an invalidate when the clip rect becomes null to ensure another draw pass occurs 1521 // and the full toolbar is drawn again. 1522 if (mClipRect == null) postInvalidate(); 1523 } 1524 } 1525 1526 @Override onStateRestored()1527 public void onStateRestored() { 1528 if (mToggleTabStackButton != null) mToggleTabStackButton.setClickable(true); 1529 } 1530 1531 @Override isReadyForTextureCapture()1532 public boolean isReadyForTextureCapture() { 1533 if (mForceTextureCapture) { 1534 return true; 1535 } 1536 return !(urlHasFocus() || mUrlFocusChangeInProgress); 1537 } 1538 1539 @Override setForceTextureCapture(boolean forceTextureCapture)1540 public boolean setForceTextureCapture(boolean forceTextureCapture) { 1541 if (forceTextureCapture) { 1542 // Only force a texture capture if the tint for the toolbar drawables is changing or 1543 // if the tab count has changed since the last texture capture. 1544 mForceTextureCapture = mLightDrawablesUsedForLastTextureCapture != useLight(); 1545 1546 if (mTabSwitcherAnimationTabStackDrawable != null && mToggleTabStackButton != null) { 1547 mForceTextureCapture = mForceTextureCapture 1548 || mTabCountForLastTextureCapture 1549 != mTabSwitcherAnimationTabStackDrawable.getTabCount(); 1550 } 1551 1552 return mForceTextureCapture; 1553 } 1554 1555 mForceTextureCapture = forceTextureCapture; 1556 return false; 1557 } 1558 1559 @Override setLayoutUpdater(Runnable layoutUpdater)1560 public void setLayoutUpdater(Runnable layoutUpdater) { 1561 mLayoutUpdater = layoutUpdater; 1562 } 1563 1564 @Override finishAnimations()1565 public void finishAnimations() { 1566 mClipRect = null; 1567 if (mTabSwitcherModeAnimation != null) { 1568 mTabSwitcherModeAnimation.end(); 1569 mTabSwitcherModeAnimation = null; 1570 } 1571 if (mDelayedTabSwitcherModeAnimation != null) { 1572 mDelayedTabSwitcherModeAnimation.end(); 1573 mDelayedTabSwitcherModeAnimation = null; 1574 } 1575 1576 // The Android framework calls onAnimationEnd() on listeners before Animator#isRunning() 1577 // returns false. Sometimes this causes the progress bar visibility to be set incorrectly. 1578 // Update the visibility now that animations are set to null. (see crbug.com/606419) 1579 updateProgressBarVisibility(); 1580 } 1581 1582 @Override getLocationBarContentRect(Rect outRect)1583 public void getLocationBarContentRect(Rect outRect) { 1584 updateLocationBarBackgroundBounds(outRect, VisualState.NORMAL); 1585 } 1586 1587 @Override onHomeButtonUpdate(boolean homeButtonEnabled)1588 public void onHomeButtonUpdate(boolean homeButtonEnabled) { 1589 mIsHomeButtonEnabled = homeButtonEnabled; 1590 updateButtonVisibility(); 1591 } 1592 1593 @Override onWindowVisibilityChanged(int visibility)1594 public void onWindowVisibilityChanged(int visibility) { 1595 super.onWindowVisibilityChanged(visibility); 1596 updateButtonVisibility(); 1597 } 1598 1599 @Override updateButtonVisibility()1600 public void updateButtonVisibility() { 1601 if (mHomeButton == null) return; 1602 1603 boolean hideHomeButton = !mIsHomeButtonEnabled 1604 || ReturnToChromeExperimentsUtil.shouldHideHomeButtonForStartSurface( 1605 isIncognito(), false /* isTablet */); 1606 if (hideHomeButton) { 1607 removeHomeButton(); 1608 } else { 1609 addHomeButton(); 1610 } 1611 } 1612 1613 @Override onTintChanged(ColorStateList tint, boolean useLight)1614 public void onTintChanged(ColorStateList tint, boolean useLight) { 1615 if (mHomeButton != null) { 1616 ApiCompatibilityUtils.setImageTintList(mHomeButton, tint); 1617 } 1618 1619 if (mToggleTabStackButton != null) { 1620 mToggleTabStackButton.setUseLightDrawables(useLight); 1621 if (mTabSwitcherAnimationTabStackDrawable != null) { 1622 mTabSwitcherAnimationTabStackDrawable.setTint(tint); 1623 } 1624 } 1625 1626 if (mOptionalButton != null && mOptionalButtonUsesTint) { 1627 ApiCompatibilityUtils.setImageTintList(mOptionalButton, tint); 1628 } 1629 1630 // TODO(amaralp): Have the LocationBar listen to tint changes. 1631 if (mLocationBar != null) mLocationBar.updateVisualsForState(); 1632 1633 if (mLayoutUpdater != null) mLayoutUpdater.run(); 1634 } 1635 1636 @Override getHomeButton()1637 public HomeButton getHomeButton() { 1638 return mHomeButton; 1639 } 1640 removeHomeButton()1641 private void removeHomeButton() { 1642 mHomeButton.setVisibility(GONE); 1643 } 1644 addHomeButton()1645 private void addHomeButton() { 1646 mHomeButton.setVisibility( 1647 urlHasFocus() || isTabSwitcherAnimationRunning() ? INVISIBLE : VISIBLE); 1648 } 1649 createEnterTabSwitcherModeAnimation()1650 private ObjectAnimator createEnterTabSwitcherModeAnimation() { 1651 ObjectAnimator enterAnimation = 1652 ObjectAnimator.ofFloat(this, mTabSwitcherModeFractionProperty, 1.f); 1653 enterAnimation.setDuration( 1654 TopToolbarCoordinator.TAB_SWITCHER_MODE_NORMAL_ANIMATION_DURATION_MS); 1655 enterAnimation.setInterpolator(Interpolators.LINEAR_INTERPOLATOR); 1656 1657 return enterAnimation; 1658 } 1659 createExitTabSwitcherAnimation(final boolean animateNormalToolbar)1660 private ObjectAnimator createExitTabSwitcherAnimation(final boolean animateNormalToolbar) { 1661 ObjectAnimator exitAnimation = 1662 ObjectAnimator.ofFloat(this, mTabSwitcherModeFractionProperty, 0.f); 1663 exitAnimation.setDuration(animateNormalToolbar 1664 ? TopToolbarCoordinator.TAB_SWITCHER_MODE_NORMAL_ANIMATION_DURATION_MS 1665 : TAB_SWITCHER_MODE_EXIT_FADE_ANIMATION_DURATION_MS); 1666 exitAnimation.setInterpolator(Interpolators.LINEAR_INTERPOLATOR); 1667 exitAnimation.addListener(new CancelAwareAnimatorListener() { 1668 @Override 1669 public void onEnd(Animator animation) { 1670 onExitTabSwitcherAnimationEnd(); 1671 } 1672 }); 1673 1674 return exitAnimation; 1675 } 1676 createPostExitTabSwitcherAnimation()1677 private ObjectAnimator createPostExitTabSwitcherAnimation() { 1678 ObjectAnimator exitAnimation = 1679 ObjectAnimator.ofFloat(this, View.TRANSLATION_Y, -getHeight(), 0.f); 1680 exitAnimation.setDuration(TAB_SWITCHER_MODE_POST_EXIT_ANIMATION_DURATION_MS); 1681 exitAnimation.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 1682 exitAnimation.addListener(new CancelAwareAnimatorListener() { 1683 @Override 1684 public void onStart(Animator animation) { 1685 updateViewsForTabSwitcherMode(); 1686 // On older builds, force an update to ensure the new visuals are used 1687 // when bringing in the toolbar. crbug.com/404571 1688 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { 1689 requestLayout(); 1690 } 1691 } 1692 1693 @Override 1694 public void onEnd(Animator animation) { 1695 mDelayedTabSwitcherModeAnimation = null; 1696 onExitTabSwitcherAnimationEnd(); 1697 } 1698 }); 1699 1700 return exitAnimation; 1701 } 1702 onExitTabSwitcherAnimationEnd()1703 private void onExitTabSwitcherAnimationEnd() { 1704 updateViewsForTabSwitcherMode(); 1705 1706 // Request a texture update to ensure a texture is captured before the user 1707 // re-enters the tab switcher. 1708 postInvalidate(); 1709 mLayoutUpdater.run(); 1710 } 1711 1712 @Override setTextureCaptureMode(boolean textureMode)1713 public void setTextureCaptureMode(boolean textureMode) { 1714 assert mTextureCaptureMode != textureMode; 1715 mTextureCaptureMode = textureMode; 1716 if (mTextureCaptureMode) { 1717 if (!hideShadowForIncognitoNtp() && !hideShadowForInterstitial() 1718 && !hideShadowForRegularNtpTextureCapture()) { 1719 mToolbarShadow.setVisibility(VISIBLE); 1720 } 1721 mPreTextureCaptureAlpha = getAlpha(); 1722 mPreTextureCaptureVisibility = getVisibility(); 1723 setAlpha(1); 1724 setVisibility(View.VISIBLE); 1725 } else { 1726 setAlpha(mPreTextureCaptureAlpha); 1727 setVisibility(mPreTextureCaptureVisibility); 1728 updateShadowVisibility(); 1729 mPreTextureCaptureAlpha = 1f; 1730 } 1731 } 1732 hideShadowForRegularNtpTextureCapture()1733 private boolean hideShadowForRegularNtpTextureCapture() { 1734 return !isIncognito() && UrlUtilities.isNTPUrl(getToolbarDataProvider().getCurrentUrl()) 1735 && mNtpSearchBoxScrollFraction < 1.f; 1736 } 1737 1738 // TODO(dtrainor): This is always true when in the tab switcher (crbug.com/710750). isTabSwitcherAnimationRunning()1739 private boolean isTabSwitcherAnimationRunning() { 1740 return mTabSwitcherState == ENTERING_TAB_SWITCHER 1741 || mTabSwitcherState == EXITING_TAB_SWITCHER; 1742 } 1743 updateViewsForTabSwitcherMode()1744 private void updateViewsForTabSwitcherMode() { 1745 setVisibility(mTabSwitcherState == STATIC_TAB ? View.VISIBLE : View.INVISIBLE); 1746 1747 updateProgressBarVisibility(); 1748 updateShadowVisibility(); 1749 updateTabSwitcherButtonRipple(); 1750 } 1751 updateProgressBarVisibility()1752 private void updateProgressBarVisibility() { 1753 getProgressBar().setVisibility(mTabSwitcherState != STATIC_TAB ? INVISIBLE : VISIBLE); 1754 } 1755 1756 @Override setContentAttached(boolean attached)1757 public void setContentAttached(boolean attached) { 1758 updateVisualsForLocationBarState(); 1759 } 1760 1761 @Override setTabSwitcherMode(boolean inTabSwitcherMode, boolean showToolbar, boolean delayAnimation, MenuButtonCoordinator menuButtonCoordinator)1762 public void setTabSwitcherMode(boolean inTabSwitcherMode, boolean showToolbar, 1763 boolean delayAnimation, MenuButtonCoordinator menuButtonCoordinator) { 1764 setTabSwitcherMode(inTabSwitcherMode, showToolbar, delayAnimation, true); 1765 } 1766 1767 /** 1768 * See {@link #setTabSwitcherMode(boolean, boolean, boolean, MenuButtonCoordinator)}. 1769 */ setTabSwitcherMode(boolean inTabSwitcherMode, boolean showToolbar, boolean delayAnimation, boolean animate)1770 public void setTabSwitcherMode(boolean inTabSwitcherMode, boolean showToolbar, 1771 boolean delayAnimation, boolean animate) { 1772 if (handleOmniboxInOverviewMode(inTabSwitcherMode)) return; 1773 1774 // If setting tab switcher mode to true and the browser is already animating or in the tab 1775 // switcher skip. 1776 if (inTabSwitcherMode 1777 && (mTabSwitcherState == TAB_SWITCHER 1778 || mTabSwitcherState == ENTERING_TAB_SWITCHER)) { 1779 return; 1780 } 1781 1782 // Likewise if exiting the tab switcher. 1783 if (!inTabSwitcherMode 1784 && (mTabSwitcherState == STATIC_TAB || mTabSwitcherState == EXITING_TAB_SWITCHER)) { 1785 return; 1786 } 1787 mTabSwitcherState = inTabSwitcherMode ? ENTERING_TAB_SWITCHER : EXITING_TAB_SWITCHER; 1788 1789 // The width of location bar depends on mTabSwitcherState so layout request is needed. See 1790 // crbug.com/974745. 1791 requestLayout(); 1792 1793 mLocationBar.getPhoneCoordinator().setUrlBarFocusable(false); 1794 1795 finishAnimations(); 1796 1797 mDelayingTabSwitcherAnimation = delayAnimation; 1798 1799 if (inTabSwitcherMode) { 1800 if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) { 1801 mUrlFocusLayoutAnimator.end(); 1802 mUrlFocusLayoutAnimator = null; 1803 // After finishing the animation, force a re-layout of the location bar, 1804 // so that the final translation position is correct (since onMeasure updates 1805 // won't happen in tab switcher mode). crbug.com/518795. 1806 layoutLocationBar(getMeasuredWidth()); 1807 updateUrlExpansionAnimation(); 1808 } 1809 1810 updateViewsForTabSwitcherMode(); 1811 mTabSwitcherModeAnimation = createEnterTabSwitcherModeAnimation(); 1812 } else { 1813 if (!mDelayingTabSwitcherAnimation) { 1814 mTabSwitcherModeAnimation = createExitTabSwitcherAnimation(showToolbar); 1815 } 1816 } 1817 1818 updateButtonsTranslationY(); 1819 mAnimateNormalToolbar = showToolbar; 1820 if (mTabSwitcherModeAnimation != null) mTabSwitcherModeAnimation.start(); 1821 1822 if (DeviceClassManager.enableAccessibilityLayout() || !animate) finishAnimations(); 1823 1824 postInvalidateOnAnimation(); 1825 } 1826 1827 /** 1828 * Enables or disables the tab switcher ripple depending on whether we are in or out of the tab 1829 * switcher mode. 1830 */ updateTabSwitcherButtonRipple()1831 private void updateTabSwitcherButtonRipple() { 1832 if (mToggleTabStackButton == null) return; 1833 if (mTabSwitcherState == ENTERING_TAB_SWITCHER) { 1834 mToggleTabStackButton.setBackgroundColor( 1835 ApiCompatibilityUtils.getColor(getResources(), android.R.color.transparent)); 1836 } else { 1837 // TODO(https://crbug.com/912358): This appears to no longer work. 1838 TypedValue outValue = new TypedValue(); 1839 1840 // The linked style here will have to be changed if it is updated in the XML. 1841 getContext().getTheme().resolveAttribute(R.style.ToolbarButton, outValue, true); 1842 mToggleTabStackButton.setBackgroundResource(outValue.resourceId); 1843 } 1844 } 1845 1846 /** 1847 * Handles animating the omnibox state when switching in or out of the tab switcher when the tab 1848 * switcher is displaying the omnibox. 1849 * @param inTabSwitcherMode Whether we are entering tab switcher. 1850 * @return Whether we handled showing the omnibox on the tab switcher. 1851 */ handleOmniboxInOverviewMode(boolean inTabSwitcherMode)1852 private boolean handleOmniboxInOverviewMode(boolean inTabSwitcherMode) { 1853 if (!getToolbarDataProvider().shouldShowLocationBarInOverviewMode()) return false; 1854 1855 if (mToggleTabStackButton != null) { 1856 boolean isGone = inTabSwitcherMode; 1857 mToggleTabStackButton.setVisibility(isGone ? GONE : VISIBLE); 1858 } 1859 1860 getMenuButtonCoordinator().setVisibility(!inTabSwitcherMode); 1861 1862 // The URL focusing animator set shouldn't be populated before native initialization. It is 1863 // possible that this function is called before native initialization when Instant Start 1864 // is enabled. Keyboard shouldn't be shown here. 1865 if (isNativeLibraryReady()) { 1866 triggerUrlFocusAnimation( 1867 inTabSwitcherMode && !urlHasFocus(), /* shouldShowKeyboard= */ false); 1868 } else { 1869 mPendingTriggerUrlFocusRequest = true; 1870 } 1871 1872 if (inTabSwitcherMode) mUrlBar.setText(""); 1873 1874 return true; 1875 } 1876 1877 @Override onTabSwitcherTransitionFinished()1878 public void onTabSwitcherTransitionFinished() { 1879 setAlpha(1.f); 1880 mClipRect = null; 1881 1882 // Detect what was being transitioned from and set the new state appropriately. 1883 if (mTabSwitcherState == EXITING_TAB_SWITCHER) { 1884 mLocationBar.getPhoneCoordinator().setUrlBarFocusable(true); 1885 mTabSwitcherState = STATIC_TAB; 1886 updateVisualsForLocationBarState(); 1887 } 1888 if (mTabSwitcherState == ENTERING_TAB_SWITCHER) mTabSwitcherState = TAB_SWITCHER; 1889 1890 // The width of location bar depends on mTabSwitcherState so layout request is needed. See 1891 // crbug.com/974745. 1892 requestLayout(); 1893 1894 mTabSwitcherModeFraction = mTabSwitcherState != STATIC_TAB ? 1.0f : 0.0f; 1895 1896 if (!mAnimateNormalToolbar) { 1897 finishAnimations(); 1898 updateVisualsForLocationBarState(); 1899 } 1900 1901 if (mDelayingTabSwitcherAnimation) { 1902 mDelayingTabSwitcherAnimation = false; 1903 mDelayedTabSwitcherModeAnimation = createPostExitTabSwitcherAnimation(); 1904 mDelayedTabSwitcherModeAnimation.start(); 1905 } else { 1906 updateViewsForTabSwitcherMode(); 1907 } 1908 1909 // Set the url bar text back after finished hiding the tab switcher. 1910 if (getToolbarDataProvider().shouldShowLocationBarInOverviewMode() 1911 && mTabSwitcherState == STATIC_TAB && getToolbarDataProvider() != null 1912 && getToolbarDataProvider().getUrlBarData() != null) { 1913 assert !getToolbarDataProvider().isInOverviewAndShowingOmnibox(); 1914 mUrlBar.setText(getToolbarDataProvider().getUrlBarData().displayText); 1915 } 1916 } 1917 1918 @Override setOnTabSwitcherClickHandler(OnClickListener listener)1919 public void setOnTabSwitcherClickHandler(OnClickListener listener) { 1920 if (mToggleTabStackButton != null) { 1921 mToggleTabStackButton.setOnTabSwitcherClickHandler(listener); 1922 } 1923 } 1924 1925 @Override setOnTabSwitcherLongClickHandler(OnLongClickListener listener)1926 void setOnTabSwitcherLongClickHandler(OnLongClickListener listener) { 1927 if (mToggleTabStackButton != null) { 1928 mToggleTabStackButton.setOnTabSwitcherLongClickHandler(listener); 1929 } 1930 } 1931 1932 @Override shouldIgnoreSwipeGesture()1933 public boolean shouldIgnoreSwipeGesture() { 1934 return super.shouldIgnoreSwipeGesture() || mUrlExpansionFraction > 0f 1935 || mNtpSearchBoxTranslation.y < 0f; 1936 } 1937 buildUrlScrollProperty( final View containerView, final boolean isContainerRtl)1938 private Property<TextView, Integer> buildUrlScrollProperty( 1939 final View containerView, final boolean isContainerRtl) { 1940 // If the RTL-ness of the container view changes during an animation, the scroll values 1941 // become invalid. If that happens, snap to the ending position and no longer update. 1942 return new Property<TextView, Integer>(Integer.class, "scrollX") { 1943 private boolean mRtlStateInvalid; 1944 1945 @Override 1946 public Integer get(TextView view) { 1947 return view.getScrollX(); 1948 } 1949 1950 @Override 1951 public void set(TextView view, Integer scrollX) { 1952 if (mRtlStateInvalid) return; 1953 boolean rtl = containerView.getLayoutDirection() == LAYOUT_DIRECTION_RTL; 1954 if (rtl != isContainerRtl) { 1955 mRtlStateInvalid = true; 1956 if (!rtl || mUrlBar.getLayout() != null) { 1957 scrollX = 0; 1958 if (rtl) { 1959 scrollX = (int) view.getLayout().getPrimaryHorizontal(0); 1960 scrollX -= view.getWidth(); 1961 } 1962 } 1963 } 1964 view.setScrollX(scrollX); 1965 } 1966 }; 1967 } 1968 populateUrlFocusingAnimatorSet(List<Animator> animators)1969 private void populateUrlFocusingAnimatorSet(List<Animator> animators) { 1970 TraceEvent.begin("ToolbarPhone.populateUrlFocusingAnimatorSet"); 1971 Animator animator = ObjectAnimator.ofFloat(this, mUrlFocusChangeFractionProperty, 1f); 1972 animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS); 1973 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 1974 animators.add(animator); 1975 1976 mLocationBar.getPhoneCoordinator().populateFadeAnimations( 1977 animators, 0, URL_FOCUS_CHANGE_ANIMATION_DURATION_MS, 0); 1978 1979 float density = getContext().getResources().getDisplayMetrics().density; 1980 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 1981 float toolbarButtonTranslationX = 1982 MathUtils.flipSignIf(mUrlFocusTranslationX, isRtl) * density; 1983 1984 animator = getMenuButtonCoordinator().getUrlFocusingAnimator(true); 1985 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 1986 animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 1987 animators.add(animator); 1988 1989 if (mToggleTabStackButton != null) { 1990 animator = ObjectAnimator.ofFloat( 1991 mToggleTabStackButton, TRANSLATION_X, toolbarButtonTranslationX); 1992 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 1993 animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 1994 animators.add(animator); 1995 1996 animator = ObjectAnimator.ofFloat(mToggleTabStackButton, ALPHA, 0); 1997 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 1998 animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 1999 animators.add(animator); 2000 } 2001 2002 if (mOptionalButton != null && mOptionalButton.getVisibility() != View.GONE) { 2003 animator = ObjectAnimator.ofFloat( 2004 mOptionalButton, TRANSLATION_X, toolbarButtonTranslationX); 2005 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2006 animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 2007 animators.add(animator); 2008 2009 animator = ObjectAnimator.ofFloat(mOptionalButton, ALPHA, 0); 2010 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2011 animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 2012 animators.add(animator); 2013 } 2014 2015 if (mToolbarShadow != null) { 2016 animator = ObjectAnimator.ofFloat(mToolbarShadow, ALPHA, 0); 2017 animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS); 2018 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2019 animators.add(animator); 2020 } 2021 TraceEvent.end("ToolbarPhone.populateUrlFocusingAnimatorSet"); 2022 } 2023 populateUrlClearFocusingAnimatorSet(List<Animator> animators)2024 private void populateUrlClearFocusingAnimatorSet(List<Animator> animators) { 2025 Animator animator = ObjectAnimator.ofFloat(this, mUrlFocusChangeFractionProperty, 0f); 2026 animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS); 2027 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2028 animators.add(animator); 2029 2030 animator = getMenuButtonCoordinator().getUrlFocusingAnimator(false); 2031 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2032 animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 2033 animators.add(animator); 2034 2035 if (mToggleTabStackButton != null) { 2036 animator = ObjectAnimator.ofFloat(mToggleTabStackButton, TRANSLATION_X, 0); 2037 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2038 animator.setStartDelay(URL_CLEAR_FOCUS_TABSTACK_DELAY_MS); 2039 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2040 animators.add(animator); 2041 2042 animator = ObjectAnimator.ofFloat(mToggleTabStackButton, ALPHA, 1); 2043 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2044 animator.setStartDelay(URL_CLEAR_FOCUS_TABSTACK_DELAY_MS); 2045 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2046 animators.add(animator); 2047 } 2048 2049 if (mOptionalButton != null && mOptionalButton.getVisibility() != View.GONE) { 2050 // TODO(twellington): it's possible that the optional button was shown while 2051 // the url bar was focused, in which case the translation x and alpha animators 2052 // are a no-op. Account for this case. 2053 animator = ObjectAnimator.ofFloat(mOptionalButton, TRANSLATION_X, 0); 2054 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2055 animator.setStartDelay(URL_CLEAR_FOCUS_EXPERIMENTAL_BUTTON_DELAY_MS); 2056 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2057 animators.add(animator); 2058 2059 animator = ObjectAnimator.ofFloat(mOptionalButton, ALPHA, 1); 2060 animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS); 2061 animator.setStartDelay(URL_CLEAR_FOCUS_EXPERIMENTAL_BUTTON_DELAY_MS); 2062 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2063 animators.add(animator); 2064 } 2065 2066 mLocationBar.getPhoneCoordinator().populateFadeAnimations( 2067 animators, URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS, URL_CLEAR_FOCUS_MENU_DELAY_MS, 1); 2068 2069 if (isLocationBarShownInNTP() && mNtpSearchBoxScrollFraction == 0f) return; 2070 2071 if (mToolbarShadow != null) { 2072 animator = ObjectAnimator.ofFloat(mToolbarShadow, ALPHA, 1); 2073 animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS); 2074 animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2075 animators.add(animator); 2076 } 2077 } 2078 2079 @Override onUrlFocusChange(final boolean hasFocus)2080 public void onUrlFocusChange(final boolean hasFocus) { 2081 super.onUrlFocusChange(hasFocus); 2082 2083 if (mToggleTabStackButton != null) mToggleTabStackButton.setClickable(!hasFocus); 2084 2085 if (getToolbarDataProvider().isInOverviewAndShowingOmnibox()) { 2086 mUrlBar.setText(""); 2087 if (!hasFocus) return; 2088 } 2089 2090 triggerUrlFocusAnimation(hasFocus, /* shouldShowKeyboard= */ hasFocus); 2091 } 2092 2093 /** 2094 * @param hasFocus Whether the URL field has gained focus. 2095 * @param shouldShowKeyboard Whether the keyboard should be shown. This value should be the same 2096 * as hasFocus by default. 2097 */ triggerUrlFocusAnimation(final boolean hasFocus, boolean shouldShowKeyboard)2098 private void triggerUrlFocusAnimation(final boolean hasFocus, boolean shouldShowKeyboard) { 2099 TraceEvent.begin("ToolbarPhone.triggerUrlFocusAnimation"); 2100 if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) { 2101 mUrlFocusLayoutAnimator.cancel(); 2102 mUrlFocusLayoutAnimator = null; 2103 } 2104 if (mOptionalButtonAnimationRunning) mOptionalButtonAnimator.end(); 2105 2106 List<Animator> animators = new ArrayList<>(); 2107 if (hasFocus) { 2108 populateUrlFocusingAnimatorSet(animators); 2109 } else { 2110 populateUrlClearFocusingAnimatorSet(animators); 2111 } 2112 mUrlFocusLayoutAnimator = new AnimatorSet(); 2113 mUrlFocusLayoutAnimator.playTogether(animators); 2114 2115 mUrlFocusChangeInProgress = true; 2116 mUrlFocusLayoutAnimator.addListener(new CancelAwareAnimatorListener() { 2117 @Override 2118 public void onStart(Animator animation) { 2119 if (!hasFocus) { 2120 mDisableLocationBarRelayout = true; 2121 } else { 2122 mLayoutLocationBarInFocusedMode = true; 2123 requestLayout(); 2124 } 2125 } 2126 2127 @Override 2128 public void onCancel(Animator animation) { 2129 if (!hasFocus) mDisableLocationBarRelayout = false; 2130 2131 mUrlFocusChangeInProgress = false; 2132 } 2133 2134 @Override 2135 public void onEnd(Animator animation) { 2136 if (!hasFocus) { 2137 mDisableLocationBarRelayout = false; 2138 mLayoutLocationBarInFocusedMode = false; 2139 requestLayout(); 2140 } 2141 mLocationBar.getPhoneCoordinator().finishUrlFocusChange( 2142 hasFocus, shouldShowKeyboard); 2143 mUrlFocusChangeInProgress = false; 2144 2145 if (getToolbarDataProvider().shouldShowLocationBarInOverviewMode()) { 2146 mLocationBar.updateStatusIcon(); 2147 } 2148 } 2149 }); 2150 mUrlFocusLayoutAnimator.start(); 2151 TraceEvent.end("ToolbarPhone.triggerUrlFocusAnimation"); 2152 } 2153 2154 @Override setTabCountProvider(TabCountProvider tabCountProvider)2155 public void setTabCountProvider(TabCountProvider tabCountProvider) { 2156 mTabCountProvider = tabCountProvider; 2157 mTabCountProvider.addObserver(this); 2158 if (mToggleTabStackButton != null) { 2159 mToggleTabStackButton.setTabCountProvider(tabCountProvider); 2160 } 2161 } 2162 2163 @Override onTabCountChanged(int numberOfTabs, boolean isIncognito)2164 public void onTabCountChanged(int numberOfTabs, boolean isIncognito) { 2165 if (mHomeButton != null) mHomeButton.setEnabled(true); 2166 2167 if (mToggleTabStackButton == null) return; 2168 2169 boolean useTabStackDrawableLight = 2170 isIncognito || ColorUtils.shouldUseLightForegroundOnBackground(getTabThemeColor()); 2171 if (mTabSwitcherAnimationTabStackDrawable == null 2172 || mIsOverlayTabStackDrawableLight != useTabStackDrawableLight) { 2173 mTabSwitcherAnimationTabStackDrawable = TabSwitcherDrawable.createTabSwitcherDrawable( 2174 getContext(), useTabStackDrawableLight); 2175 int[] stateSet = {android.R.attr.state_enabled}; 2176 mTabSwitcherAnimationTabStackDrawable.setState(stateSet); 2177 mIsOverlayTabStackDrawableLight = useTabStackDrawableLight; 2178 } 2179 2180 if (mTabSwitcherAnimationTabStackDrawable != null) { 2181 mTabSwitcherAnimationTabStackDrawable.updateForTabCount(numberOfTabs, isIncognito); 2182 } 2183 2184 if (getToolbarDataProvider().isInOverviewAndShowingOmnibox() 2185 && getToolbarDataProvider().shouldShowLocationBarInOverviewMode()) { 2186 mUrlBar.setText(""); 2187 } 2188 } 2189 2190 /** 2191 * Get the theme color for the currently active tab. This is not affected by the tab switcher's 2192 * theme color. 2193 * @return The current tab's theme color. 2194 */ getTabThemeColor()2195 private int getTabThemeColor() { 2196 if (getToolbarDataProvider() != null) return getToolbarDataProvider().getPrimaryColor(); 2197 return getToolbarColorForVisualState( 2198 isIncognito() ? VisualState.INCOGNITO : VisualState.NORMAL); 2199 } 2200 2201 @Override onTabContentViewChanged()2202 public void onTabContentViewChanged() { 2203 super.onTabContentViewChanged(); 2204 updateNtpAnimationState(); 2205 updateVisualsForLocationBarState(); 2206 } 2207 2208 @Override onTabOrModelChanged()2209 public void onTabOrModelChanged() { 2210 super.onTabOrModelChanged(); 2211 updateNtpAnimationState(); 2212 updateVisualsForLocationBarState(); 2213 } 2214 isVisualStateValidForBrandColorTransition(@isualState int state)2215 private static boolean isVisualStateValidForBrandColorTransition(@VisualState int state) { 2216 return state == VisualState.NORMAL || state == VisualState.BRAND_COLOR; 2217 } 2218 2219 @Override onPrimaryColorChanged(boolean shouldAnimate)2220 public void onPrimaryColorChanged(boolean shouldAnimate) { 2221 super.onPrimaryColorChanged(shouldAnimate); 2222 if (mBrandColorTransitionActive) mBrandColorTransitionAnimation.end(); 2223 2224 final int initialColor = mToolbarBackground.getColor(); 2225 final int finalColor = getToolbarDataProvider().getPrimaryColor(); 2226 if (initialColor == finalColor) return; 2227 2228 final int initialLocationBarColor = getLocationBarColorForToolbarColor(initialColor); 2229 final int finalLocationBarColor = getLocationBarColorForToolbarColor(finalColor); 2230 2231 if (!isVisualStateValidForBrandColorTransition(mVisualState)) return; 2232 2233 if (!shouldAnimate) { 2234 updateToolbarBackground(finalColor); 2235 return; 2236 } 2237 2238 boolean shouldUseOpaque = ColorUtils.shouldUseOpaqueTextboxBackground(finalColor); 2239 final int initialAlpha = mLocationBarBackgroundAlpha; 2240 final int finalAlpha = shouldUseOpaque ? 255 : LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA; 2241 final boolean shouldAnimateAlpha = initialAlpha != finalAlpha; 2242 mBrandColorTransitionAnimation = 2243 ValueAnimator.ofFloat(0, 1).setDuration(THEME_COLOR_TRANSITION_DURATION); 2244 mBrandColorTransitionAnimation.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2245 mBrandColorTransitionAnimation.addUpdateListener(new AnimatorUpdateListener() { 2246 @Override 2247 public void onAnimationUpdate(ValueAnimator animation) { 2248 float fraction = animation.getAnimatedFraction(); 2249 if (shouldAnimateAlpha) { 2250 mLocationBarBackgroundAlpha = 2251 (int) (MathUtils.interpolate(initialAlpha, finalAlpha, fraction)); 2252 } 2253 updateToolbarBackground( 2254 ColorUtils.getColorWithOverlay(initialColor, finalColor, fraction)); 2255 updateModernLocationBarColor(ColorUtils.getColorWithOverlay( 2256 initialLocationBarColor, finalLocationBarColor, fraction)); 2257 } 2258 }); 2259 mBrandColorTransitionAnimation.addListener(new CancelAwareAnimatorListener() { 2260 @Override 2261 public void onEnd(Animator animation) { 2262 mBrandColorTransitionActive = false; 2263 updateVisualsForLocationBarState(); 2264 } 2265 }); 2266 mBrandColorTransitionAnimation.start(); 2267 mBrandColorTransitionActive = true; 2268 if (mLayoutUpdater != null) mLayoutUpdater.run(); 2269 } 2270 updateNtpAnimationState()2271 private void updateNtpAnimationState() { 2272 NewTabPageDelegate ntpDelegate = getToolbarDataProvider().getNewTabPageDelegate(); 2273 2274 // Store previous NTP scroll before calling reset as that clears this value. 2275 boolean wasShowingNtp = ntpDelegate.wasShowingNtp(); 2276 float previousNtpScrollFraction = mNtpSearchBoxScrollFraction; 2277 2278 resetNtpAnimationValues(); 2279 ntpDelegate.setSearchBoxScrollListener(this::onNtpScrollChanged); 2280 if (ntpDelegate.isLocationBarShown()) { 2281 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 2282 NtpSearchBoxDrawable ntpSearchBox = new NtpSearchBoxDrawable(getContext(), this); 2283 ntpDelegate.setSearchBoxBackground(ntpSearchBox); 2284 mActiveLocationBarBackground = ntpSearchBox; 2285 } 2286 2287 requestLayout(); 2288 } else if (wasShowingNtp) { 2289 // Convert the previous NTP scroll progress to URL focus progress because that 2290 // will give a nicer transition animation from the expanded NTP omnibox to the 2291 // collapsed normal omnibox on other non-NTP pages. 2292 if (mTabSwitcherState == STATIC_TAB && previousNtpScrollFraction > 0f) { 2293 mUrlFocusChangeFraction = 2294 Math.max(previousNtpScrollFraction, mUrlFocusChangeFraction); 2295 triggerUrlFocusAnimation(/* hasFocus= */ false, /* shouldShowKeyboard= */ false); 2296 } 2297 requestLayout(); 2298 } 2299 } 2300 2301 @Override onDefaultSearchEngineChanged()2302 public void onDefaultSearchEngineChanged() { 2303 super.onDefaultSearchEngineChanged(); 2304 // Post an update for the toolbar state, which will allow all other listeners 2305 // for the search engine change to update before we check on the state of the 2306 // world for a UI update. 2307 // TODO(tedchoc): Move away from updating based on the search engine change and instead 2308 // add the toolbar as a listener to the NewTabPage and udpate only when 2309 // it notifies the listeners that it has changed its state. 2310 post(new Runnable() { 2311 @Override 2312 public void run() { 2313 updateVisualsForLocationBarState(); 2314 updateNtpAnimationState(); 2315 } 2316 }); 2317 } 2318 2319 @Override handleFindLocationBarStateChange(boolean showing)2320 public void handleFindLocationBarStateChange(boolean showing) { 2321 setVisibility(showing ? View.GONE 2322 : mTabSwitcherState == STATIC_TAB ? View.VISIBLE : View.INVISIBLE); 2323 } 2324 isLocationBarShownInNTP()2325 private boolean isLocationBarShownInNTP() { 2326 return getToolbarDataProvider().getNewTabPageDelegate().isLocationBarShown(); 2327 } 2328 isLocationBarCurrentlyShown()2329 private boolean isLocationBarCurrentlyShown() { 2330 return !isLocationBarShownInNTP() || mUrlExpansionFraction > 0; 2331 } 2332 2333 /** 2334 * Update the visibility of the toolbar shadow. 2335 */ updateShadowVisibility()2336 private void updateShadowVisibility() { 2337 boolean shouldDrawShadow = shouldDrawShadow(); 2338 int shadowVisibility = shouldDrawShadow ? View.VISIBLE : View.INVISIBLE; 2339 2340 if (mToolbarShadow != null && mToolbarShadow.getVisibility() != shadowVisibility) { 2341 mToolbarShadow.setVisibility(shadowVisibility); 2342 } 2343 } 2344 2345 /** 2346 * @return Whether the toolbar shadow should be drawn. 2347 */ shouldDrawShadow()2348 private boolean shouldDrawShadow() { 2349 // TODO(twellington): Move this shadow state information to ToolbarDataProvider and show 2350 // shadow when incognito NTP is scrolled. 2351 return mTabSwitcherState == STATIC_TAB && !hideShadowForIncognitoNtp() 2352 && !hideShadowForInterstitial() 2353 && (getToolbarDataProvider() != null 2354 && !getToolbarDataProvider().isInOverviewAndShowingOmnibox()); 2355 } 2356 hideShadowForIncognitoNtp()2357 private boolean hideShadowForIncognitoNtp() { 2358 return isIncognito() && UrlUtilities.isNTPUrl(getToolbarDataProvider().getCurrentUrl()); 2359 } 2360 hideShadowForInterstitial()2361 private boolean hideShadowForInterstitial() { 2362 return getToolbarDataProvider() != null && getToolbarDataProvider().getTab() != null 2363 && (getToolbarDataProvider().getTab().isShowingErrorPage()); 2364 } 2365 computeVisualState()2366 private @VisualState int computeVisualState() { 2367 if (isLocationBarShownInNTP()) return VisualState.NEW_TAB_NORMAL; 2368 if (isIncognito()) return VisualState.INCOGNITO; 2369 if (getToolbarDataProvider().isUsingBrandColor()) return VisualState.BRAND_COLOR; 2370 return VisualState.NORMAL; 2371 } 2372 2373 /** 2374 * @return The color that progress bar should use. 2375 */ getProgressBarColor()2376 private int getProgressBarColor() { 2377 return getToolbarDataProvider().getPrimaryColor(); 2378 } 2379 updateVisualsForLocationBarState()2380 private void updateVisualsForLocationBarState() { 2381 TraceEvent.begin("ToolbarPhone.updateVisualsForLocationBarState"); 2382 // These are used to skip setting state unnecessarily while in the tab switcher. 2383 boolean inOrEnteringStaticTab = 2384 mTabSwitcherState == STATIC_TAB || mTabSwitcherState == EXITING_TAB_SWITCHER; 2385 2386 @VisualState 2387 int newVisualState = computeVisualState(); 2388 2389 // If we are navigating to or from a brand color, allow the transition animation 2390 // to run to completion as it will handle the triggering this path again and committing 2391 // the proper visual state when it finishes. Brand color transitions are only valid 2392 // between normal non-incognito pages and brand color pages, so if the visual states 2393 // do not match then cancel the animation below. 2394 if (mBrandColorTransitionActive && isVisualStateValidForBrandColorTransition(mVisualState) 2395 && isVisualStateValidForBrandColorTransition(newVisualState)) { 2396 TraceEvent.end("ToolbarPhone.updateVisualsForLocationBarState"); 2397 return; 2398 } else if (mBrandColorTransitionAnimation != null 2399 && mBrandColorTransitionAnimation.isRunning()) { 2400 mBrandColorTransitionAnimation.end(); 2401 } 2402 2403 boolean visualStateChanged = mVisualState != newVisualState; 2404 2405 int currentPrimaryColor = getToolbarDataProvider().getPrimaryColor(); 2406 int themeColorForProgressBar = getProgressBarColor(); 2407 2408 // If The page is native force the use of the standard theme for the progress bar. 2409 if (getToolbarDataProvider() != null && getToolbarDataProvider().getTab() != null 2410 && getToolbarDataProvider().getTab().isNativePage()) { 2411 @VisualState 2412 int visualState = isIncognito() ? VisualState.INCOGNITO : VisualState.NORMAL; 2413 themeColorForProgressBar = getToolbarColorForVisualState(visualState); 2414 } 2415 2416 if (mVisualState == VisualState.BRAND_COLOR && !visualStateChanged) { 2417 boolean unfocusedLocationBarUsesTransparentBg = 2418 !ColorUtils.shouldUseOpaqueTextboxBackground(currentPrimaryColor); 2419 if (unfocusedLocationBarUsesTransparentBg != mUnfocusedLocationBarUsesTransparentBg) { 2420 visualStateChanged = true; 2421 } else { 2422 updateToolbarBackgroundFromState(VisualState.BRAND_COLOR); 2423 getProgressBar().setThemeColor(themeColorForProgressBar, isIncognito()); 2424 } 2425 } 2426 2427 mVisualState = newVisualState; 2428 2429 // Refresh the toolbar texture. 2430 if ((mVisualState == VisualState.BRAND_COLOR || visualStateChanged) 2431 && mLayoutUpdater != null) { 2432 mLayoutUpdater.run(); 2433 } 2434 updateShadowVisibility(); 2435 updateUrlExpansionAnimation(); 2436 2437 // This exception is to prevent early change of theme color when exiting the tab switcher 2438 // since currently visual state does not map correctly to tab switcher state. See 2439 // https://crbug.com/832594 for more info. 2440 if (mTabSwitcherState != EXITING_TAB_SWITCHER) { 2441 updateToolbarBackgroundFromState(mVisualState); 2442 } 2443 2444 if (!visualStateChanged) { 2445 if (mVisualState == VisualState.NEW_TAB_NORMAL) { 2446 updateNtpTransitionAnimation(); 2447 } else { 2448 resetNtpAnimationValues(); 2449 } 2450 TraceEvent.end("ToolbarPhone.updateVisualsForLocationBarState"); 2451 return; 2452 } 2453 2454 mUnfocusedLocationBarUsesTransparentBg = false; 2455 mLocationBarBackgroundAlpha = 255; 2456 getProgressBar().setThemeColor(themeColorForProgressBar, isIncognito()); 2457 2458 if (isIncognito()) { 2459 mLocationBarBackgroundAlpha = LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA; 2460 } else if (mVisualState == VisualState.BRAND_COLOR) { 2461 mUnfocusedLocationBarUsesTransparentBg = 2462 !ColorUtils.shouldUseOpaqueTextboxBackground(currentPrimaryColor); 2463 mLocationBarBackgroundAlpha = mUnfocusedLocationBarUsesTransparentBg 2464 ? LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA 2465 : 255; 2466 } 2467 2468 updateModernLocationBarColor(getLocationBarColorForToolbarColor(currentPrimaryColor)); 2469 2470 mLocationBar.updateVisualsForState(); 2471 2472 // We update the alpha before comparing the visual state as we need to change 2473 // its value when entering and exiting TabSwitcher mode. 2474 if (isLocationBarShownInNTP() && inOrEnteringStaticTab) { 2475 updateNtpTransitionAnimation(); 2476 } 2477 2478 getMenuButtonCoordinator().setVisibility(true); 2479 2480 DrawableCompat.setTint(mLocationBarBackground, 2481 isIncognito() ? Color.WHITE 2482 : ApiCompatibilityUtils.getColor( 2483 getResources(), R.color.toolbar_text_box_background)); 2484 TraceEvent.end("ToolbarPhone.updateVisualsForLocationBarState"); 2485 } 2486 2487 @Override getLocationBar()2488 public LocationBar getLocationBar() { 2489 return mLocationBar; 2490 } 2491 2492 @Override updateOptionalButton(ButtonData buttonData)2493 void updateOptionalButton(ButtonData buttonData) { 2494 if (mOptionalButton == null) { 2495 ViewStub viewStub = findViewById(R.id.optional_button_stub); 2496 mOptionalButton = (ImageButton) viewStub.inflate(); 2497 2498 if (!isMenuButtonPresent()) mOptionalButton.setPadding(0, 0, 0, 0); 2499 mOptionalButtonTranslation = getResources().getDimensionPixelSize( 2500 R.dimen.toolbar_optional_button_animation_translation); 2501 if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) mOptionalButtonTranslation *= -1; 2502 } else if (mOptionalButtonAnimationRunning) { 2503 // TODO(https://crbug.com/865801): refine this logic to allow for same-button updates, 2504 // e.g. swapping in a new drawable part way through the animation. 2505 mOptionalButtonAnimator.end(); 2506 } 2507 2508 mOptionalButton.setOnClickListener(buttonData.onClickListener); 2509 mOptionalButton.setImageDrawable(buttonData.drawable); 2510 mOptionalButton.setContentDescription( 2511 getContext().getResources().getString(buttonData.contentDescriptionResId)); 2512 mOptionalButton.setEnabled(buttonData.isEnabled); 2513 2514 mOptionalButtonUsesTint = buttonData.supportsTinting; 2515 if (mOptionalButtonUsesTint) { 2516 ApiCompatibilityUtils.setImageTintList(mOptionalButton, getTint()); 2517 } else { 2518 ApiCompatibilityUtils.setImageTintList(mOptionalButton, null); 2519 } 2520 mOptionalButtonLayoutListener = () -> requestLayoutHostUpdateForOptionalButton(); 2521 if (mTabSwitcherState == STATIC_TAB) { 2522 if (!mUrlFocusChangeInProgress && !urlHasFocus() 2523 && mOptionalButton.getVisibility() == View.GONE) { 2524 runShowOptionalButtonAnimation(); 2525 } else { 2526 mOptionalButton.setVisibility(View.VISIBLE); 2527 onOptionalButtonAnimationEnd(); 2528 } 2529 } else { 2530 mOptionalButton.setVisibility(View.VISIBLE); 2531 getViewTreeObserver().addOnGlobalLayoutListener(mOptionalButtonLayoutListener); 2532 } 2533 } 2534 2535 @Override hideOptionalButton()2536 void hideOptionalButton() { 2537 // mLayoutLocationBarWithoutExtraButton implies that the hide animation is currently 2538 // running. 2539 if (mOptionalButton == null || mOptionalButton.getVisibility() == View.GONE 2540 || mLayoutLocationBarWithoutExtraButton) { 2541 return; 2542 } 2543 2544 boolean transitioningAwayFromLocationBarInNTP = 2545 getToolbarDataProvider().getNewTabPageDelegate().transitioningAwayFromLocationBar(); 2546 2547 if (mTabSwitcherState == STATIC_TAB && !mUrlFocusChangeInProgress && !urlHasFocus() 2548 && !transitioningAwayFromLocationBarInNTP) { 2549 runHideOptionalButtonsAnimators(); 2550 } else { 2551 mOptionalButton.setVisibility(View.GONE); 2552 getViewTreeObserver().addOnGlobalLayoutListener(mOptionalButtonLayoutListener); 2553 } 2554 } 2555 2556 @Override 2557 @VisibleForTesting getOptionalButtonView()2558 public View getOptionalButtonView() { 2559 return mOptionalButton; 2560 } 2561 2562 /** 2563 * Whether the menu button is visible. Used as a proxy for whether there are end toolbar 2564 * buttons besides the optional button. 2565 */ isMenuButtonPresent()2566 private boolean isMenuButtonPresent() { 2567 return getMenuButtonCoordinator().isShown(); 2568 } 2569 requestLayoutHostUpdateForOptionalButton()2570 private void requestLayoutHostUpdateForOptionalButton() { 2571 if (mLayoutUpdater != null) mLayoutUpdater.run(); 2572 getViewTreeObserver().removeOnGlobalLayoutListener(mOptionalButtonLayoutListener); 2573 } 2574 2575 /** 2576 * Runs an animation that fades in the optional button while shortening the location bar 2577 * background. 2578 */ runShowOptionalButtonAnimation()2579 private void runShowOptionalButtonAnimation() { 2580 if (mOptionalButtonAnimationRunning) mOptionalButtonAnimator.end(); 2581 2582 List<Animator> animators = new ArrayList<>(); 2583 2584 mLocBarWidthChangeFraction = 1.f; 2585 Animator widthChangeAnimator = 2586 ObjectAnimator.ofFloat(this, mLocBarWidthChangeFractionProperty, 0.f); 2587 widthChangeAnimator.setDuration(LOC_BAR_WIDTH_CHANGE_ANIMATION_DURATION_MS); 2588 widthChangeAnimator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2589 animators.add(widthChangeAnimator); 2590 2591 mOptionalButton.setAlpha(0.f); 2592 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mOptionalButton, View.ALPHA, 1.f); 2593 buttonAnimator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2594 buttonAnimator.setStartDelay(EXPERIMENTAL_ICON_ANIMATION_DELAY_MS); 2595 buttonAnimator.setDuration(EXPERIMENTAL_ICON_ANIMATION_DURATION_MS); 2596 animators.add(buttonAnimator); 2597 2598 mOptionalButton.setTranslationX(mOptionalButtonTranslation); 2599 ObjectAnimator buttonTranslationAnimator = 2600 ObjectAnimator.ofFloat(mOptionalButton, View.TRANSLATION_X, 0); 2601 buttonTranslationAnimator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2602 buttonTranslationAnimator.setStartDelay(EXPERIMENTAL_ICON_ANIMATION_DELAY_MS); 2603 buttonTranslationAnimator.setDuration(EXPERIMENTAL_ICON_ANIMATION_DURATION_MS); 2604 animators.add(buttonTranslationAnimator); 2605 2606 mOptionalButtonAnimator = new AnimatorSet(); 2607 mOptionalButtonAnimator.addListener(new CancelAwareAnimatorListener() { 2608 @Override 2609 public void onStart(Animator animation) { 2610 mDisableLocationBarRelayout = true; 2611 mOptionalButtonAnimationRunning = true; 2612 mOptionalButton.setVisibility(View.VISIBLE); 2613 } 2614 2615 @Override 2616 public void onEnd(Animator animation) { 2617 onOptionalButtonAnimationEnd(); 2618 mDisableLocationBarRelayout = false; 2619 mOptionalButtonAnimationRunning = false; 2620 getViewTreeObserver().addOnGlobalLayoutListener(mOptionalButtonLayoutListener); 2621 requestLayout(); 2622 } 2623 }); 2624 mOptionalButtonAnimator.playTogether(animators); 2625 mOptionalButtonAnimator.start(); 2626 } 2627 2628 /** 2629 * Runs an animation that fades out the optional button while lengthening the location bar 2630 * background. 2631 */ runHideOptionalButtonsAnimators()2632 private void runHideOptionalButtonsAnimators() { 2633 if (mOptionalButtonAnimationRunning) mOptionalButtonAnimator.end(); 2634 2635 List<Animator> animators = new ArrayList<>(); 2636 mLocBarWidthChangeFraction = 0.f; 2637 Animator widthChangeAnimator = 2638 ObjectAnimator.ofFloat(this, mLocBarWidthChangeFractionProperty, 1.f); 2639 widthChangeAnimator.setDuration(LOC_BAR_WIDTH_CHANGE_ANIMATION_DURATION_MS); 2640 widthChangeAnimator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE); 2641 animators.add(widthChangeAnimator); 2642 2643 mOptionalButton.setAlpha(1.f); 2644 ObjectAnimator buttonAnimator = ObjectAnimator.ofFloat(mOptionalButton, View.ALPHA, 0.f); 2645 buttonAnimator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 2646 buttonAnimator.setDuration(EXPERIMENTAL_ICON_ANIMATION_DURATION_MS); 2647 animators.add(buttonAnimator); 2648 2649 mOptionalButton.setTranslationX(0); 2650 ObjectAnimator buttonTranslationAnimator = ObjectAnimator.ofFloat( 2651 mOptionalButton, View.TRANSLATION_X, mOptionalButtonTranslation); 2652 buttonTranslationAnimator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE); 2653 buttonTranslationAnimator.setDuration(EXPERIMENTAL_ICON_ANIMATION_DURATION_MS); 2654 animators.add(buttonTranslationAnimator); 2655 2656 mOptionalButtonAnimator = new AnimatorSet(); 2657 mOptionalButtonAnimator.addListener(new CancelAwareAnimatorListener() { 2658 @Override 2659 public void onStart(Animator animation) { 2660 mLayoutLocationBarWithoutExtraButton = true; 2661 mOptionalButtonAnimationRunning = true; 2662 requestLayout(); 2663 } 2664 2665 @Override 2666 public void onEnd(Animator animation) { 2667 onOptionalButtonAnimationEnd(); 2668 mOptionalButton.setVisibility(View.GONE); 2669 mLayoutLocationBarWithoutExtraButton = false; 2670 mOptionalButtonAnimationRunning = false; 2671 getViewTreeObserver().addOnGlobalLayoutListener(mOptionalButtonLayoutListener); 2672 } 2673 }); 2674 mOptionalButtonAnimator.playTogether(animators); 2675 mOptionalButtonAnimator.start(); 2676 } 2677 2678 /** 2679 * Resets the alpha and translation X for all views affected by the animations for showing or 2680 * hiding buttons. 2681 */ onOptionalButtonAnimationEnd()2682 private void onOptionalButtonAnimationEnd() { 2683 mOptionalButtonAnimator = null; 2684 mOptionalButton.setAlpha(1.f); 2685 mOptionalButton.setTranslationX(0); 2686 } 2687 2688 /** 2689 * Custom drawable that allows sharing the NTP search box drawable between the toolbar and the 2690 * NTP. This allows animations to continue as the drawable is switched between the two owning 2691 * views. 2692 */ 2693 private static class NtpSearchBoxDrawable extends DrawableWrapper { 2694 private final Drawable.Callback mCallback; 2695 2696 private int mBoundsLeft; 2697 private int mBoundsTop; 2698 private int mBoundsRight; 2699 private int mBoundsBottom; 2700 private boolean mPendingBoundsUpdateFromToolbar; 2701 private boolean mDrawnByNtp; 2702 2703 /** 2704 * Constructs the NTP search box drawable. 2705 * 2706 * @param context The context used to inflate the drawable. 2707 * @param callback The callback to be notified on changes ot the drawable. 2708 */ NtpSearchBoxDrawable(Context context, Drawable.Callback callback)2709 public NtpSearchBoxDrawable(Context context, Drawable.Callback callback) { 2710 super(ApiCompatibilityUtils.getDrawable( 2711 context.getResources(), R.drawable.ntp_search_box)); 2712 mCallback = callback; 2713 setCallback(mCallback); 2714 } 2715 2716 /** 2717 * Mark that the pending bounds update is coming from the toolbar. 2718 */ markPendingBoundsUpdateFromToolbar()2719 void markPendingBoundsUpdateFromToolbar() { 2720 mPendingBoundsUpdateFromToolbar = true; 2721 } 2722 2723 /** 2724 * Reset the bounds of the drawable to the last bounds received that was not marked from 2725 * the toolbar. 2726 */ resetBoundsToLastNonToolbar()2727 void resetBoundsToLastNonToolbar() { 2728 setBounds(mBoundsLeft, mBoundsTop, mBoundsRight, mBoundsBottom); 2729 } 2730 2731 @Override setBounds(int left, int top, int right, int bottom)2732 public void setBounds(int left, int top, int right, int bottom) { 2733 super.setBounds(left, top, right, bottom); 2734 if (!mPendingBoundsUpdateFromToolbar) { 2735 mBoundsLeft = left; 2736 mBoundsTop = top; 2737 mBoundsRight = right; 2738 mBoundsBottom = bottom; 2739 mDrawnByNtp = true; 2740 } else { 2741 mDrawnByNtp = false; 2742 } 2743 mPendingBoundsUpdateFromToolbar = false; 2744 } 2745 2746 @Override setVisible(boolean visible, boolean restart)2747 public boolean setVisible(boolean visible, boolean restart) { 2748 // Ignore visibility changes. The NTP can toggle the visibility based on the scroll 2749 // position of the page, so we simply ignore all of this as we expect the drawable to 2750 // be visible at all times of the NTP. 2751 return false; 2752 } 2753 2754 @Override getCallback()2755 public Callback getCallback() { 2756 return mDrawnByNtp ? super.getCallback() : mCallback; 2757 } 2758 } 2759 cancelAnimations()2760 private void cancelAnimations() { 2761 if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) { 2762 mUrlFocusLayoutAnimator.cancel(); 2763 } 2764 2765 if (mBrandColorTransitionAnimation != null && mBrandColorTransitionAnimation.isRunning()) { 2766 mBrandColorTransitionAnimation.cancel(); 2767 } 2768 2769 if (mOptionalButtonAnimator != null && mOptionalButtonAnimator.isRunning()) { 2770 mOptionalButtonAnimator.cancel(); 2771 } 2772 } 2773 } 2774