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