// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.toolbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.SystemClock;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.util.AttributeSet;
import android.util.Property;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupWindow.OnDismissListener;
import android.widget.TextView;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.SysUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.Invalidator;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.fullscreen.FullscreenManager;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.omnibox.LocationBarPhone;
import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.util.ColorUtils;
import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.browser.widget.TintedImageButton;
import org.chromium.chrome.browser.widget.newtab.NewTabButton;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.interpolators.BakedBezierInterpolator;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Phone specific toolbar implementation.
*/
public class ToolbarPhone extends ToolbarLayout
implements Invalidator.Client, OnClickListener, OnLongClickListener,
NewTabPage.OnSearchBoxScrollListener {
/** The amount of time transitioning from one theme color to another should take in ms. */
public static final long THEME_COLOR_TRANSITION_DURATION = 250;
public static final int URL_FOCUS_CHANGE_ANIMATION_DURATION_MS = 250;
private static final int URL_FOCUS_TOOLBAR_BUTTONS_TRANSLATION_X_DP = 10;
private static final int URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS = 100;
private static final int URL_CLEAR_FOCUS_TABSTACK_DELAY_MS = 200;
private static final int URL_CLEAR_FOCUS_MENU_DELAY_MS = 250;
private static final int TAB_SWITCHER_MODE_ENTER_ANIMATION_DURATION_MS = 200;
private static final int TAB_SWITCHER_MODE_EXIT_NORMAL_ANIMATION_DURATION_MS = 200;
private static final int TAB_SWITCHER_MODE_EXIT_FADE_ANIMATION_DURATION_MS = 100;
private static final int TAB_SWITCHER_MODE_POST_EXIT_ANIMATION_DURATION_MS = 100;
private static final float UNINITIALIZED_PERCENT = -1f;
/** States that the toolbar can be in regarding the tab switcher. */
private static final int STATIC_TAB = 0;
private static final int TAB_SWITCHER = 1;
private static final int ENTERING_TAB_SWITCHER = 2;
private static final int EXITING_TAB_SWITCHER = 3;
@ViewDebug.ExportedProperty(category = "chrome", mapping = {
@ViewDebug.IntToString(from = STATIC_TAB, to = "STATIC_TAB"),
@ViewDebug.IntToString(from = TAB_SWITCHER, to = "TAB_SWITCHER"),
@ViewDebug.IntToString(from = ENTERING_TAB_SWITCHER, to = "ENTERING_TAB_SWITCHER"),
@ViewDebug.IntToString(from = EXITING_TAB_SWITCHER, to = "EXITING_TAB_SWITCHER")
})
static final int LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA = 51;
private static final Interpolator NTP_SEARCH_BOX_EXPANSION_INTERPOLATOR =
new FastOutSlowInInterpolator();
private LocationBarPhone mLocationBar;
private ViewGroup mToolbarButtonsContainer;
private ImageView mToggleTabStackButton;
private NewTabButton mNewTabButton;
private TintedImageButton mHomeButton;
private TextView mUrlBar;
private View mUrlActionContainer;
private ImageView mToolbarShadow;
private final int mProgressBackBackgroundColorWhite;
private ObjectAnimator mTabSwitcherModeAnimation;
private ObjectAnimator mDelayedTabSwitcherModeAnimation;
private final List<View> mTabSwitcherModeViews = new ArrayList<>();
private final Set<View> mBrowsingModeViews = new HashSet<>();
@ViewDebug.ExportedProperty(category = "chrome")
private int mTabSwitcherState;
// This determines whether or not the toolbar draws as expected (false) or whether it always
// draws as if it's showing the non-tabswitcher, non-animating toolbar. This is used in grabbing
// a bitmap to use as a texture representation of this view.
@ViewDebug.ExportedProperty(category = "chrome")
private boolean mTextureCaptureMode;
private boolean mForceTextureCapture;
private boolean mUseLightDrawablesForTextureCapture;
private boolean mLightDrawablesUsedForLastTextureCapture;
@ViewDebug.ExportedProperty(category = "chrome")
private boolean mAnimateNormalToolbar;
@ViewDebug.ExportedProperty(category = "chrome")
private boolean mDelayingTabSwitcherAnimation;
private ColorDrawable mTabSwitcherAnimationBgOverlay;
private TabSwitcherDrawable mTabSwitcherAnimationTabStackDrawable;
private Drawable mTabSwitcherAnimationMenuDrawable;
private Drawable mTabSwitcherAnimationMenuBadgeDarkDrawable;
private Drawable mTabSwitcherAnimationMenuBadgeLightDrawable;
// Value that determines the amount of transition from the normal toolbar mode to TabSwitcher
// mode. 0 = entirely in normal mode and 1.0 = entirely in TabSwitcher mode. In between values
// can be used for animating between the two view modes.
@ViewDebug.ExportedProperty(category = "chrome")
private float mTabSwitcherModePercent = 0;
// Used to clip the toolbar during the fade transition into and out of TabSwitcher mode. Only
// used when |mAnimateNormalToolbar| is false.
@ViewDebug.ExportedProperty(category = "chrome")
private Rect mClipRect;
private OnClickListener mTabSwitcherListener;
private OnClickListener mNewTabListener;
@ViewDebug.ExportedProperty(category = "chrome")
private boolean mUrlFocusChangeInProgress;
/** 1.0 is 100% focused, 0 is completely unfocused */
@ViewDebug.ExportedProperty(category = "chrome")
private float mUrlFocusChangePercent;
/**
* The degree to which the omnibox has expanded to full width, either because it is getting
* focused or the NTP search box is being scrolled up. Note that in the latter case, the actual
* width of the omnibox is not interpolated linearly from this value. The value will be the
* maximum of {@link #mUrlFocusChangePercent} and {@link #mNtpSearchBoxScrollPercent}.
*/
@ViewDebug.ExportedProperty(category = "chrome")
private float mUrlExpansionPercent;
private AnimatorSet mUrlFocusLayoutAnimator;
private boolean mDisableLocationBarRelayout;
private boolean mLayoutLocationBarInFocusedMode;
private int mUnfocusedLocationBarLayoutWidth;
private int mUnfocusedLocationBarLayoutLeft;
private boolean mUnfocusedLocationBarUsesTransparentBg;
private int mLocationBarBackgroundAlpha = 255;
private float mNtpSearchBoxScrollPercent = UNINITIALIZED_PERCENT;
private ColorDrawable mToolbarBackground;
/** The omnibox background (white with a shadow). */
private Drawable mLocationBarBackground;
private boolean mForceDrawLocationBarBackground;
private TabSwitcherDrawable mTabSwitcherButtonDrawable;
private TabSwitcherDrawable mTabSwitcherButtonDrawableLight;
private final int mLightModeDefaultColor;
private final int mDarkModeDefaultColor;
/** The boundaries of the omnibox, without the NTP-specific offset applied. */
private final Rect mLocationBarBackgroundBounds = new Rect();
private final Rect mLocationBarBackgroundPadding = new Rect();
private final Rect mBackgroundOverlayBounds = new Rect();
/** Offset applied to the bounds of the omnibox if we are showing a New Tab Page. */
private final Rect mLocationBarBackgroundNtpOffset = new Rect();
/**
* Offsets applied to the <i>contents</i> of the omnibox if we are showing a New Tab Page.
* This can be different from {@link #mLocationBarBackgroundNtpOffset} due to the fact that we
* extend the omnibox horizontally beyond the screen boundaries when focused, to hide its
* rounded corners.
*/
private float mLocationBarNtpOffsetLeft;
private float mLocationBarNtpOffsetRight;
private final Rect mNtpSearchBoxBounds = new Rect();
private final Point mNtpSearchBoxTranslation = new Point();
private final int mToolbarSidePadding;
private final int mLocationBarVerticalMargin;
private final int mLocationBarBackgroundCornerRadius;
private ValueAnimator mBrandColorTransitionAnimation;
private boolean mBrandColorTransitionActive;
private boolean mIsHomeButtonEnabled;
private LayoutUpdateHost mLayoutUpdateHost;
/** Callout for the tab switcher button. */
private TabSwitcherCallout mTabSwitcherCallout;
/** Whether or not we've checked if the TabSwitcherCallout needs to be shown. */
private boolean mHasCheckedIfTabSwitcherCalloutIsNecessary;
/** Manages when the Toolbar hides and unhides. */
private FullscreenManager mFullscreenManager;
/** Token held when the TabSwitcherCallout is displayed to prevent the Toolbar from hiding. */
private int mFullscreenCalloutToken = FullscreenManager.INVALID_TOKEN;
/**
* Used to specify the visual state of the toolbar.
*/
private enum VisualState {
TAB_SWITCHER_INCOGNITO,
TAB_SWITCHER_NORMAL,
NORMAL,
INCOGNITO,
BRAND_COLOR,
NEW_TAB_NORMAL
}
private VisualState mVisualState = VisualState.NORMAL;
private VisualState mOverlayDrawablesVisualState;
private boolean mUseLightToolbarDrawables;
private NewTabPage mVisibleNewTabPage;
private float mPreTextureCaptureAlpha = 1f;
private boolean mIsOverlayTabStackDrawableLight;
// The following are some properties used during animation. We use explicit property classes
// to avoid the cost of reflection for each animation setup.
private final Property<ToolbarPhone, Float> mUrlFocusChangePercentProperty =
new Property<ToolbarPhone, Float>(Float.class, "") {
@Override
public Float get(ToolbarPhone object) {
return object.mUrlFocusChangePercent;
}
@Override
public void set(ToolbarPhone object, Float value) {
setUrlFocusChangePercent(value);
}
};
private final Property<ToolbarPhone, Float> mTabSwitcherModePercentProperty =
new Property<ToolbarPhone, Float>(Float.class, "") {
@Override
public Float get(ToolbarPhone object) {
return object.mTabSwitcherModePercent;
}
@Override
public void set(ToolbarPhone object, Float value) {
object.mTabSwitcherModePercent = value;
triggerPaintInvalidate(ToolbarPhone.this);
}
};
/**
* Constructs a ToolbarPhone object.
* @param context The Context in which this View object is created.
* @param attrs The AttributeSet that was specified with this View.
*/
public ToolbarPhone(Context context, AttributeSet attrs) {
super(context, attrs);
mToolbarSidePadding = getResources().getDimensionPixelOffset(
R.dimen.toolbar_edge_padding);
mLocationBarVerticalMargin =
getResources().getDimensionPixelOffset(R.dimen.location_bar_vertical_margin);
mLocationBarBackgroundCornerRadius =
getResources().getDimensionPixelOffset(R.dimen.location_bar_corner_radius);
mProgressBackBackgroundColorWhite = ApiCompatibilityUtils.getColor(getResources(),
R.color.progress_bar_background_white);
mLightModeDefaultColor =
ApiCompatibilityUtils.getColor(getResources(), R.color.light_mode_tint);
mDarkModeDefaultColor =
ApiCompatibilityUtils.getColor(getResources(), R.color.dark_mode_tint);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mLocationBar = (LocationBarPhone) findViewById(R.id.location_bar);
mToolbarButtonsContainer = (ViewGroup) findViewById(R.id.toolbar_buttons);
mHomeButton = (TintedImageButton) findViewById(R.id.home_button);
mUrlBar = (TextView) findViewById(R.id.url_bar);
mUrlActionContainer = findViewById(R.id.url_action_container);
mBrowsingModeViews.add(mLocationBar);
mToolbarBackground = new ColorDrawable(getToolbarColorForVisualState(VisualState.NORMAL));
mTabSwitcherAnimationBgOverlay =
new ColorDrawable(getToolbarColorForVisualState(VisualState.NORMAL));
mLocationBarBackground =
ApiCompatibilityUtils.getDrawable(getResources(), R.drawable.textbox);
mLocationBarBackground.getPadding(mLocationBarBackgroundPadding);
mLocationBar.setPadding(
mLocationBarBackgroundPadding.left, mLocationBarBackgroundPadding.top,
mLocationBarBackgroundPadding.right, mLocationBarBackgroundPadding.bottom);
setLayoutTransition(null);
mMenuButtonWrapper.setVisibility(View.VISIBLE);
inflateTabSwitchingResources();
setWillNotDraw(false);
}
private void inflateTabSwitchingResources() {
mToggleTabStackButton = (ImageView) findViewById(R.id.tab_switcher_button);
mNewTabButton = (NewTabButton) findViewById(R.id.new_tab_button);
mToggleTabStackButton.setClickable(false);
Resources resources = getResources();
mTabSwitcherButtonDrawable =
TabSwitcherDrawable.createTabSwitcherDrawable(resources, false);
mTabSwitcherButtonDrawableLight =
TabSwitcherDrawable.createTabSwitcherDrawable(resources, true);
mToggleTabStackButton.setImageDrawable(mTabSwitcherButtonDrawable);
mTabSwitcherModeViews.add(mNewTabButton);
// Ensure that the new tab button will not draw over the toolbar buttons if the
// translated string is long. Set a margin to the size of the toolbar button container
// for the new tab button.
WindowManager wm = (WindowManager) getContext().getSystemService(
Context.WINDOW_SERVICE);
Point screenSize = new Point();
wm.getDefaultDisplay().getSize(screenSize);
mToolbarButtonsContainer.measure(
MeasureSpec.makeMeasureSpec(screenSize.x, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(screenSize.y, MeasureSpec.AT_MOST));
ApiCompatibilityUtils.setMarginEnd(getFrameLayoutParams(mNewTabButton),
mToolbarButtonsContainer.getMeasuredWidth());
}
private void enableTabSwitchingResources() {
mToggleTabStackButton.setOnClickListener(this);
mToggleTabStackButton.setOnLongClickListener(this);
mToggleTabStackButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
if (mMenuButton != null && mMenuButton.isShown()) {
return mMenuButton;
} else {
return getCurrentTabView();
}
}
@Override
public View getNextFocusBackward() {
return findViewById(R.id.url_bar);
}
});
mNewTabButton.setOnClickListener(this);
}
@Override
protected boolean onMenuButtonTouchEvent(View v, MotionEvent event) {
dismissTabSwitcherCallout();
return super.onMenuButtonTouchEvent(v, event);
}
/**
* Sets up click and key listeners once we have native library available to handle clicks.
*/
@Override
public void onNativeLibraryReady() {
super.onNativeLibraryReady();
getLocationBar().onNativeLibraryReady();
enableTabSwitchingResources();
mHomeButton.setOnClickListener(this);
mMenuButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
return getCurrentTabView();
}
@Override
public View getNextFocusBackward() {
return mToggleTabStackButton;
}
@Override
protected boolean handleEnterKeyPress() {
return getMenuButtonHelper().onEnterKeyPress(mMenuButton);
}
});
onHomeButtonUpdate(HomepageManager.isHomepageEnabled(getContext()));
updateVisualsForToolbarState();
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// If the NTP is partially scrolled, prevent all touch events to the child views. This
// is to not allow a secondary touch event to trigger entering the tab switcher, which
// can lead to really odd snapshots and transitions to the switcher.
if (mNtpSearchBoxScrollPercent != 0f
&& mNtpSearchBoxScrollPercent != 1f
&& mNtpSearchBoxScrollPercent != UNINITIALIZED_PERCENT) {
return true;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
// Forward touch events to the NTP if the toolbar is moved away but the search box hasn't
// reached the top of the page yet.
if (mNtpSearchBoxTranslation.y < 0 && mLocationBar.getTranslationY() > 0) {
NewTabPage newTabPage = getToolbarDataProvider().getNewTabPageForCurrentTab();
// No null check -- the toolbar should not be moved if we are not on an NTP.
return newTabPage.getView().dispatchTouchEvent(ev);
}
return super.onTouchEvent(ev);
}
@Override
public void onClick(View v) {
// Don't allow clicks while the omnibox is being focused.
if (mLocationBar != null && mLocationBar.hasFocus()) return;
if (mToggleTabStackButton == v) {
// The button is clickable before the native library is loaded
// and the listener is setup.
if (mToggleTabStackButton != null && mToggleTabStackButton.isClickable()
&& mTabSwitcherListener != null) {
dismissTabSwitcherCallout();
cancelAppMenuUpdateBadgeAnimation();
mTabSwitcherListener.onClick(mToggleTabStackButton);
RecordUserAction.record("MobileToolbarShowStackView");
}
} else if (mNewTabButton == v) {
v.setEnabled(false);
if (mNewTabListener != null) {
mNewTabListener.onClick(v);
RecordUserAction.record("MobileToolbarStackViewNewTab");
RecordUserAction.record("MobileNewTabOpened");
// TODO(kkimlabs): Record UMA action for homepage button.
}
} else if (mHomeButton == v) {
openHomepage();
}
}
@Override
public boolean onLongClick(View v) {
CharSequence description = null;
if (v == mToggleTabStackButton) {
description = getResources().getString(R.string.open_tabs);
} else {
return false;
}
return showAccessibilityToast(v, description);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!mDisableLocationBarRelayout) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean changed = layoutLocationBar(MeasureSpec.getSize(widthMeasureSpec));
if (mTabSwitcherState == STATIC_TAB) updateUrlExpansionAnimation();
if (!changed) return;
} else {
updateUnfocusedLocationBarLayoutParams();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void updateUnfocusedLocationBarLayoutParams() {
boolean hasVisibleViewPriorToUrlBar = false;
for (int i = 0; i < mLocationBar.getChildCount(); i++) {
View child = mLocationBar.getChildAt(i);
if (child == mUrlBar) break;
if (child.getVisibility() != GONE) {
hasVisibleViewPriorToUrlBar = true;
break;
}
}
int leftViewBounds = getViewBoundsLeftOfLocationBar(mVisualState);
int rightViewBounds = getViewBoundsRightOfLocationBar(mVisualState);
if (!hasVisibleViewPriorToUrlBar) {
if (ApiCompatibilityUtils.isLayoutRtl(mLocationBar)) {
rightViewBounds -= mToolbarSidePadding;
} else {
leftViewBounds += mToolbarSidePadding;
}
}
// Add spacing between the end of the URL and the edge of the omnibox drawable.
// This only applies if there is no end aligned view that should be visible
// while the omnibox is unfocused.
if (ApiCompatibilityUtils.isLayoutRtl(mLocationBar)) {
leftViewBounds += mToolbarSidePadding;
} else {
rightViewBounds -= mToolbarSidePadding;
}
mUnfocusedLocationBarLayoutWidth = rightViewBounds - leftViewBounds;
mUnfocusedLocationBarLayoutLeft = leftViewBounds;
}
/**
* @return The background drawable for the fullscreen overlay.
*/
@VisibleForTesting
ColorDrawable getOverlayDrawable() {
return mTabSwitcherAnimationBgOverlay;
}
/**
* @return The background drawable for the toolbar view.
*/
@VisibleForTesting
ColorDrawable getBackgroundDrawable() {
return mToolbarBackground;
}
@SuppressLint("RtlHardcoded")
private boolean layoutLocationBar(int containerWidth) {
// Note that Toolbar's direction depends on system layout direction while
// LocationBar's direction depends on its text inside.
FrameLayout.LayoutParams locationBarLayoutParams =
getFrameLayoutParams(getLocationBar().getContainerView());
// Chrome prevents layout_gravity="left" from being defined in XML, but it simplifies
// the logic, so it is manually specified here.
locationBarLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
int width = 0;
int leftMargin = 0;
// Always update the unfocused layout params regardless of whether we are using
// those in this current layout pass as they are needed for animations.
updateUnfocusedLocationBarLayoutParams();
if (mLayoutLocationBarInFocusedMode || mVisualState == VisualState.NEW_TAB_NORMAL) {
int priorVisibleWidth = 0;
for (int i = 0; i < mLocationBar.getChildCount(); i++) {
View child = mLocationBar.getChildAt(i);
if (child == mLocationBar.getFirstViewVisibleWhenFocused()) break;
if (child.getVisibility() == GONE) continue;
priorVisibleWidth += child.getMeasuredWidth();
}
width = containerWidth - (2 * mToolbarSidePadding) + priorVisibleWidth;
if (ApiCompatibilityUtils.isLayoutRtl(mLocationBar)) {
leftMargin = mToolbarSidePadding;
} else {
leftMargin = -priorVisibleWidth + mToolbarSidePadding;
}
} else {
width = mUnfocusedLocationBarLayoutWidth;
leftMargin = mUnfocusedLocationBarLayoutLeft;
}
boolean changed = false;
changed |= (width != locationBarLayoutParams.width);
locationBarLayoutParams.width = width;
changed |= (leftMargin != locationBarLayoutParams.leftMargin);
locationBarLayoutParams.leftMargin = leftMargin;
return changed;
}
private int getViewBoundsLeftOfLocationBar(VisualState visualState) {
// Uses getMeasuredWidth()s instead of getLeft() because this is called in onMeasure
// and the layout values have not yet been set.
if (visualState == VisualState.NEW_TAB_NORMAL) {
return 0;
} else if (ApiCompatibilityUtils.isLayoutRtl(this)) {
return Math.max(
mToolbarSidePadding, mToolbarButtonsContainer.getMeasuredWidth());
} else {
return getBoundsAfterAccountingForLeftButton();
}
}
private int getBoundsAfterAccountingForLeftButton() {
int padding = mToolbarSidePadding;
if (mHomeButton.getVisibility() != GONE) padding = mHomeButton.getMeasuredWidth();
return padding;
}
private int getViewBoundsRightOfLocationBar(VisualState visualState) {
// Uses getMeasuredWidth()s instead of getRight() because this is called in onMeasure
// and the layout values have not yet been set.
if (visualState == VisualState.NEW_TAB_NORMAL) {
return getMeasuredWidth();
} else if (ApiCompatibilityUtils.isLayoutRtl(this)) {
return getMeasuredWidth() - getBoundsAfterAccountingForLeftButton();
} else {
int margin = Math.max(
mToolbarSidePadding, mToolbarButtonsContainer.getMeasuredWidth());
return getMeasuredWidth() - margin;
}
}
private void updateToolbarBackground(int color) {
mToolbarBackground.setColor(color);
invalidate();
}
private void updateToolbarBackground(VisualState visualState) {
updateToolbarBackground(getToolbarColorForVisualState(visualState));
}
private int getToolbarColorForVisualState(final VisualState visualState) {
Resources res = getResources();
switch (visualState) {
case NEW_TAB_NORMAL:
return Color.TRANSPARENT;
case NORMAL:
return ApiCompatibilityUtils.getColor(res, R.color.default_primary_color);
case INCOGNITO:
return ApiCompatibilityUtils.getColor(res, R.color.incognito_primary_color);
case BRAND_COLOR:
return getToolbarDataProvider().getPrimaryColor();
case TAB_SWITCHER_NORMAL:
case TAB_SWITCHER_INCOGNITO:
return ApiCompatibilityUtils.getColor(res, R.color.tab_switcher_background);
default:
assert false;
return ApiCompatibilityUtils.getColor(res, R.color.default_primary_color);
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (!mTextureCaptureMode && mToolbarBackground.getColor() != Color.TRANSPARENT) {
// Update to compensate for orientation changes.
mToolbarBackground.setBounds(0, 0, getWidth(), getHeight());
mToolbarBackground.draw(canvas);
}
if (mLocationBarBackground != null
&& (mLocationBar.getVisibility() == VISIBLE || mTextureCaptureMode)) {
updateLocationBarBackgroundBounds(mLocationBarBackgroundBounds, mVisualState);
}
if (mTextureCaptureMode) {
drawTabSwitcherAnimationOverlay(canvas, 0.f);
} else {
boolean tabSwitcherAnimationFinished = false;
if (mTabSwitcherModeAnimation != null) {
tabSwitcherAnimationFinished = !mTabSwitcherModeAnimation.isRunning();
// Perform the fade logic before super.dispatchDraw(canvas) so that we can properly
// set the values before the draw happens.
if (!mAnimateNormalToolbar) {
drawTabSwitcherFadeAnimation(
tabSwitcherAnimationFinished, mTabSwitcherModePercent);
}
}
super.dispatchDraw(canvas);
if (mTabSwitcherModeAnimation != null) {
// Perform the overlay logic after super.dispatchDraw(canvas) as we need to draw on
// top of the current views.
if (mAnimateNormalToolbar) {
drawTabSwitcherAnimationOverlay(canvas, mTabSwitcherModePercent);
}
// Clear the animation.
if (tabSwitcherAnimationFinished) mTabSwitcherModeAnimation = null;
}
}
}
// NewTabPage.OnSearchBoxScrollListener
@Override
public void onNtpScrollChanged(float scrollPercentage) {
// TODO(peconn): Clear up the animation transition calculations so that the parts that
// depend on the absolute scroll value (such as the Toolbar location) are separate from the
// parts that depend on the fakebox transition percentage (such as the omnibox width and
// opacity).
// At the moment, we disable the check below because these two concepts are not
// separate and we want to still update the parts that depend on scroll value when the
// transition percentage is not changed.
if (scrollPercentage == mNtpSearchBoxScrollPercent
&& !getToolbarDataProvider().getNewTabPageForCurrentTab().isCardsUiEnabled()) {
return;
}
mNtpSearchBoxScrollPercent = scrollPercentage;
updateUrlExpansionPercent();
updateUrlExpansionAnimation();
}
/**
* Calculate the bounds for the location bar background and set them to {@code out}.
*/
private void updateLocationBarBackgroundBounds(Rect out, VisualState visualState) {
// Calculate the visible boundaries of the left and right most child views of the
// location bar.
float expansion = visualState == VisualState.NEW_TAB_NORMAL ? 1 : mUrlExpansionPercent;
int leftViewPosition = (int) MathUtils.interpolate(
getViewBoundsLeftOfLocationBar(visualState),
-mLocationBarBackgroundCornerRadius,
expansion);
int rightViewPosition = (int) MathUtils.interpolate(
getViewBoundsRightOfLocationBar(visualState),
getWidth() + mLocationBarBackgroundCornerRadius,
expansion);
// The bounds are set by the following:
// - The left most visible location bar child view.
// - The top of the viewport is aligned with the top of the location bar.
// - The right most visible location bar child view.
// - The bottom of the viewport is aligned with the bottom of the location bar.
// Additional padding can be applied for use during animations.
int verticalMargin = (int) MathUtils.interpolate(mLocationBarVerticalMargin, 0, expansion);
out.set(leftViewPosition,
mLocationBar.getTop() + verticalMargin,
rightViewPosition,
mLocationBar.getBottom() - verticalMargin);
}
/**
* Updates percentage of current the URL focus change animation.
* @param percent 1.0 is 100% focused, 0 is completely unfocused.
*/
private void setUrlFocusChangePercent(float percent) {
mUrlFocusChangePercent = percent;
updateUrlExpansionPercent();
updateUrlExpansionAnimation();
}
private void updateUrlExpansionPercent() {
mUrlExpansionPercent = Math.max(mNtpSearchBoxScrollPercent, mUrlFocusChangePercent);
assert mUrlExpansionPercent >= 0;
assert mUrlExpansionPercent <= 1;
}
/**
* Updates the parameters relating to expanding the location bar, as the result of either a
* focus change or scrolling the New Tab Page.
*/
private void updateUrlExpansionAnimation() {
if (mTabSwitcherState != STATIC_TAB) {
mToolbarButtonsContainer.setVisibility(VISIBLE);
return;
}
FrameLayout.LayoutParams locationBarLayoutParams = getFrameLayoutParams(mLocationBar);
int currentLeftMargin = locationBarLayoutParams.leftMargin;
int currentWidth = locationBarLayoutParams.width;
float locationBarBaseTranslationX = mUnfocusedLocationBarLayoutLeft - currentLeftMargin;
boolean isLocationBarRtl = ApiCompatibilityUtils.isLayoutRtl(mLocationBar);
if (isLocationBarRtl) {
locationBarBaseTranslationX += mUnfocusedLocationBarLayoutWidth - currentWidth;
}
locationBarBaseTranslationX *= 1f - mUrlExpansionPercent;
mLocationBarBackgroundNtpOffset.setEmpty();
mLocationBarNtpOffsetLeft = 0;
mLocationBarNtpOffsetRight = 0;
Tab currentTab = getToolbarDataProvider().getTab();
if (currentTab != null) {
NewTabPage ntp = getToolbarDataProvider().getNewTabPageForCurrentTab();
// Explicitly use the focus change percentage here because it applies scroll
// compensation that only applies during focus animations.
if (ntp != null && mUrlFocusChangeInProgress) {
ntp.setUrlFocusChangeAnimationPercent(mUrlFocusChangePercent);
}
if (isLocationBarShownInNTP()) {
updateNtpTransitionAnimation();
} else {
// Reset these values in case we transitioned to a different page during the
// transition.
resetNtpAnimationValues();
}
}
boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
float locationBarTranslationX;
// Get the padding straight from the location bar instead of
// |mLocationBarBackgroundPadding|, because it might be different in incognito mode.
if (isRtl) {
locationBarTranslationX = locationBarBaseTranslationX
+ mLocationBarNtpOffsetRight - mLocationBar.getPaddingRight();
} else {
locationBarTranslationX = locationBarBaseTranslationX
+ mLocationBarNtpOffsetLeft + mLocationBar.getPaddingLeft();
}
mLocationBar.setTranslationX(locationBarTranslationX);
// Negate the location bar translation to keep the URL action container in the same
// place during the focus expansion.
float urlActionsTranslationX = 0;
if (!isLocationBarRtl || isRtl) {
urlActionsTranslationX = -locationBarBaseTranslationX;
}
if (isRtl) {
urlActionsTranslationX += mLocationBarNtpOffsetLeft - mLocationBarNtpOffsetRight;
} else {
urlActionsTranslationX += mLocationBarNtpOffsetRight - mLocationBarNtpOffsetLeft;
}
mUrlActionContainer.setTranslationX(urlActionsTranslationX);
mLocationBar.setUrlFocusChangePercent(mUrlExpansionPercent);
// Ensure the buttons are invisible after focusing the omnibox to prevent them from
// accepting click events.
int toolbarButtonVisibility = mUrlExpansionPercent == 1f ? INVISIBLE : VISIBLE;
mToolbarButtonsContainer.setVisibility(toolbarButtonVisibility);
if (mHomeButton.getVisibility() != GONE) {
mHomeButton.setVisibility(toolbarButtonVisibility);
}
// Force an invalidation of the location bar to properly handle the clipping of the URL
// bar text as a result of the url action container translations.
mLocationBar.invalidate();
invalidate();
}
/**
* Reset the parameters for the New Tab Page transition animation (expanding the location bar as
* a result of scrolling the New Tab Page) to their default values.
*/
private void resetNtpAnimationValues() {
mLocationBarBackgroundNtpOffset.setEmpty();
mNtpSearchBoxTranslation.set(0, 0);
mLocationBar.setTranslationY(0);
if (!mUrlFocusChangeInProgress) {
mToolbarButtonsContainer.setTranslationY(0);
mHomeButton.setTranslationY(0);
}
mToolbarShadow.setAlpha(1f);
mLocationBar.setAlpha(1);
mForceDrawLocationBarBackground = false;
mLocationBarBackgroundAlpha = 255;
if (isIncognito()
|| (mUnfocusedLocationBarUsesTransparentBg && !mUrlFocusChangeInProgress
&& !mLocationBar.hasFocus())) {
mLocationBarBackgroundAlpha = LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA;
}
setAncestorsShouldClipChildren(true);
mNtpSearchBoxScrollPercent = UNINITIALIZED_PERCENT;
updateUrlExpansionPercent();
}
/**
* Updates the parameters of the New Tab Page transition animation (expanding the location bar
* as a result of scrolling the New Tab Page).
*/
private void updateNtpTransitionAnimation() {
// Skip if in or entering tab switcher mode.
if (mTabSwitcherState == TAB_SWITCHER || mTabSwitcherState == ENTERING_TAB_SWITCHER) return;
setAncestorsShouldClipChildren(mUrlExpansionPercent == 0f);
mToolbarShadow.setAlpha(0f);
NewTabPage ntp = getToolbarDataProvider().getNewTabPageForCurrentTab();
ntp.getSearchBoxBounds(mNtpSearchBoxBounds, mNtpSearchBoxTranslation);
int locationBarTranslationY =
Math.max(0, (mNtpSearchBoxBounds.top - mLocationBar.getTop()));
mLocationBar.setTranslationY(locationBarTranslationY);
updateButtonsTranslationY();
// Linearly interpolate between the bounds of the search box on the NTP and the omnibox
// background bounds. |shrinkage| is the scaling factor for the offset -- if it's 1, we are
// shrinking the omnibox down to the size of the search box.
float shrinkage;
if (ntp.isCardsUiEnabled()) {
shrinkage = 1f
- NTP_SEARCH_BOX_EXPANSION_INTERPOLATOR.getInterpolation(mUrlExpansionPercent);
} else {
// During the transition from middle of the NTP to the top, keep the omnibox drawing
// at the same size of the search box for first 40% of the scroll transition.
shrinkage = Math.min(1f, (1f - mUrlExpansionPercent) * 1.66667f);
}
int leftBoundDifference = mNtpSearchBoxBounds.left - mLocationBarBackgroundBounds.left;
int rightBoundDifference = mNtpSearchBoxBounds.right - mLocationBarBackgroundBounds.right;
mLocationBarBackgroundNtpOffset.set(
Math.round(leftBoundDifference * shrinkage),
locationBarTranslationY,
Math.round(rightBoundDifference * shrinkage),
locationBarTranslationY);
// The omnibox background bounds are outset by |mLocationBarBackgroundCornerRadius| in the
// fully expanded state (and only there!) to hide the rounded corners, so undo that before
// applying the shrinkage factor.
mLocationBarNtpOffsetLeft =
(leftBoundDifference - mLocationBarBackgroundCornerRadius) * shrinkage;
mLocationBarNtpOffsetRight =
(rightBoundDifference + mLocationBarBackgroundCornerRadius) * shrinkage;
mLocationBarBackgroundAlpha = mUrlExpansionPercent > 0f ? 255 : 0;
mForceDrawLocationBarBackground = mLocationBarBackgroundAlpha > 0;
float relativeAlpha = mLocationBarBackgroundAlpha / 255f;
mLocationBar.setAlpha(relativeAlpha);
// The search box on the NTP is visible if our omnibox is invisible, and vice-versa.
ntp.setSearchBoxAlpha(1f - relativeAlpha);
if (!ntp.isCardsUiEnabled()) {
ntp.setSearchProviderLogoAlpha(Math.max(1f - mUrlExpansionPercent * 2.5f, 0f));
}
}
/**
* Update the y translation of the buttons to make it appear as if they were scrolling with
* the new tab page.
*/
private void updateButtonsTranslationY() {
int transY = mTabSwitcherState == STATIC_TAB ? Math.min(mNtpSearchBoxTranslation.y, 0) : 0;
mToolbarButtonsContainer.setTranslationY(transY);
mHomeButton.setTranslationY(transY);
}
private void setAncestorsShouldClipChildren(boolean clip) {
if (!isLocationBarShownInNTP()) return;
ViewGroup parent = this;
while (parent != null) {
parent.setClipChildren(clip);
if (!(parent.getParent() instanceof ViewGroup)) break;
if (parent.getId() == android.R.id.content) break;
parent = (ViewGroup) parent.getParent();
}
}
private void drawTabSwitcherFadeAnimation(boolean animationFinished, float progress) {
setAlpha(progress);
if (animationFinished) {
mClipRect = null;
} else if (mClipRect == null) {
mClipRect = new Rect();
}
if (mClipRect != null) mClipRect.set(0, 0, getWidth(), (int) (getHeight() * progress));
}
/**
* When entering and exiting the TabSwitcher mode, we fade out or fade in the browsing
* mode of the toolbar on top of the TabSwitcher mode version of it. We do this by
* drawing all of the browsing mode views on top of the android view.
*/
private void drawTabSwitcherAnimationOverlay(Canvas canvas, float animationProgress) {
if (!isNativeLibraryReady()) return;
float floatAlpha = 1 - animationProgress;
int rgbAlpha = (int) (255 * floatAlpha);
canvas.save();
canvas.translate(0, -animationProgress * mBackgroundOverlayBounds.height());
canvas.clipRect(mBackgroundOverlayBounds);
float previousAlpha = 0.f;
if (mHomeButton.getVisibility() != View.GONE) {
// Draw the New Tab button used in the URL view.
previousAlpha = mHomeButton.getAlpha();
mHomeButton.setAlpha(previousAlpha * floatAlpha);
drawChild(canvas, mHomeButton, SystemClock.uptimeMillis());
mHomeButton.setAlpha(previousAlpha);
}
// Draw the location/URL bar.
previousAlpha = mLocationBar.getAlpha();
mLocationBar.setAlpha(previousAlpha * floatAlpha);
// If the location bar is now fully transparent, do not bother drawing it.
if (mLocationBar.getAlpha() != 0) {
drawChild(canvas, mLocationBar, SystemClock.uptimeMillis());
}
mLocationBar.setAlpha(previousAlpha);
// Draw the tab stack button and associated text.
translateCanvasToView(this, mToolbarButtonsContainer, canvas);
if (mTabSwitcherAnimationTabStackDrawable != null && mToggleTabStackButton != null
&& mUrlExpansionPercent != 1f) {
// Draw the tab stack button image.
canvas.save();
translateCanvasToView(mToolbarButtonsContainer, mToggleTabStackButton, canvas);
int backgroundWidth = mToggleTabStackButton.getDrawable().getIntrinsicWidth();
int backgroundHeight = mToggleTabStackButton.getDrawable().getIntrinsicHeight();
int backgroundLeft = (mToggleTabStackButton.getWidth()
- mToggleTabStackButton.getPaddingLeft()
- mToggleTabStackButton.getPaddingRight() - backgroundWidth) / 2;
backgroundLeft += mToggleTabStackButton.getPaddingLeft();
int backgroundTop = (mToggleTabStackButton.getHeight()
- mToggleTabStackButton.getPaddingTop()
- mToggleTabStackButton.getPaddingBottom() - backgroundHeight) / 2;
backgroundTop += mToggleTabStackButton.getPaddingTop();
canvas.translate(backgroundLeft, backgroundTop);
mTabSwitcherAnimationTabStackDrawable.setAlpha(rgbAlpha);
mTabSwitcherAnimationTabStackDrawable.draw(canvas);
canvas.restore();
}
// Draw the menu button if necessary.
if (!mShowMenuBadge && mTabSwitcherAnimationMenuDrawable != null
&& mUrlExpansionPercent != 1f) {
mTabSwitcherAnimationMenuDrawable.setBounds(
mMenuButton.getPaddingLeft(), mMenuButton.getPaddingTop(),
mMenuButton.getWidth() - mMenuButton.getPaddingRight(),
mMenuButton.getHeight() - mMenuButton.getPaddingBottom());
translateCanvasToView(mToolbarButtonsContainer, mMenuButton, canvas);
mTabSwitcherAnimationMenuDrawable.setAlpha(rgbAlpha);
int color = mUseLightDrawablesForTextureCapture
? mLightModeDefaultColor
: mDarkModeDefaultColor;
mTabSwitcherAnimationMenuDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
mTabSwitcherAnimationMenuDrawable.draw(canvas);
}
// Draw the menu badge if necessary.
Drawable badgeDrawable = mUseLightDrawablesForTextureCapture
? mTabSwitcherAnimationMenuBadgeLightDrawable
: mTabSwitcherAnimationMenuBadgeDarkDrawable;
if (mShowMenuBadge && badgeDrawable != null && mUrlExpansionPercent != 1f) {
badgeDrawable.setBounds(
mMenuBadge.getPaddingLeft(), mMenuBadge.getPaddingTop(),
mMenuBadge.getWidth() - mMenuBadge.getPaddingRight(),
mMenuBadge.getHeight() - mMenuBadge.getPaddingBottom());
translateCanvasToView(mToolbarButtonsContainer, mMenuBadge, canvas);
badgeDrawable.setAlpha(rgbAlpha);
badgeDrawable.draw(canvas);
}
mLightDrawablesUsedForLastTextureCapture = mUseLightDrawablesForTextureCapture;
canvas.restore();
}
@Override
public void doInvalidate() {
postInvalidateOnAnimation();
}
/**
* Translates the canvas to ensure the specified view's coordinates are at 0, 0.
*
* @param from The view the canvas is currently translated to.
* @param to The view to translate to.
* @param canvas The canvas to be translated.
*
* @throws IllegalArgumentException if {@code from} is not an ancestor of {@code to}.
*/
private static void translateCanvasToView(View from, View to, Canvas canvas)
throws IllegalArgumentException {
assert from != null;
assert to != null;
while (to != from) {
canvas.translate(to.getLeft(), to.getTop());
if (!(to.getParent() instanceof View)) {
throw new IllegalArgumentException("View 'to' was not a desendent of 'from'.");
}
to = (View) to.getParent();
}
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (child == mLocationBar) return drawLocationBar(canvas, drawingTime);
boolean clipped = false;
if (mLocationBarBackground != null
&& ((mTabSwitcherState == STATIC_TAB && !mTabSwitcherModeViews.contains(child))
|| (mTabSwitcherState != STATIC_TAB
&& mBrowsingModeViews.contains(child)))) {
canvas.save();
int translationY = (int) mLocationBar.getTranslationY();
int clipTop = mLocationBarBackgroundBounds.top - mLocationBarBackgroundPadding.top
+ translationY;
if (mUrlExpansionPercent != 0f && clipTop < child.getBottom()) {
// For other child views, use the inverse clipping of the URL viewport.
// Only necessary during animations.
// Hardware mode does not support unioned clip regions, so clip using the
// appropriate bounds based on whether the child is to the left or right of the
// location bar.
boolean isLeft = (child == mNewTabButton
|| child == mHomeButton) ^ LocalizationUtils.isLayoutRtl();
int clipBottom = mLocationBarBackgroundBounds.bottom
+ mLocationBarBackgroundPadding.bottom + translationY;
boolean verticalClip = false;
if (translationY > 0f) {
clipTop = child.getTop();
clipBottom = clipTop;
verticalClip = true;
}
if (isLeft) {
int clipRight = verticalClip ? child.getMeasuredWidth()
: mLocationBarBackgroundBounds.left
- mLocationBarBackgroundPadding.left;
canvas.clipRect(0, clipTop, clipRight, clipBottom);
} else {
int clipLeft = verticalClip ? 0 : mLocationBarBackgroundBounds.right
+ mLocationBarBackgroundPadding.right;
canvas.clipRect(clipLeft, clipTop, getMeasuredWidth(), clipBottom);
}
}
clipped = true;
}
boolean retVal = super.drawChild(canvas, child, drawingTime);
if (clipped) canvas.restore();
return retVal;
}
private boolean drawLocationBar(Canvas canvas, long drawingTime) {
boolean clipped = false;
if (mLocationBarBackground != null
&& (mTabSwitcherState == STATIC_TAB || mTextureCaptureMode)) {
canvas.save();
int backgroundAlpha;
if (mTabSwitcherModeAnimation != null) {
// Fade out/in the location bar towards the beginning of the animations to avoid
// large jumps of stark white.
backgroundAlpha =
(int) (Math.pow(mLocationBar.getAlpha(), 3) * mLocationBarBackgroundAlpha);
} else if (getToolbarDataProvider().isUsingBrandColor()
&& !mBrandColorTransitionActive) {
backgroundAlpha = mUnfocusedLocationBarUsesTransparentBg
? (int) (MathUtils.interpolate(LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA,
255, mUrlExpansionPercent))
: 255;
} else {
backgroundAlpha = mLocationBarBackgroundAlpha;
}
mLocationBarBackground.setAlpha(backgroundAlpha);
if ((mLocationBar.getAlpha() > 0 || mForceDrawLocationBarBackground)
&& !mTextureCaptureMode) {
mLocationBarBackground.setBounds(
mLocationBarBackgroundBounds.left + mLocationBarBackgroundNtpOffset.left
- mLocationBarBackgroundPadding.left,
mLocationBarBackgroundBounds.top + mLocationBarBackgroundNtpOffset.top
- mLocationBarBackgroundPadding.top,
mLocationBarBackgroundBounds.right + mLocationBarBackgroundNtpOffset.right
+ mLocationBarBackgroundPadding.right,
mLocationBarBackgroundBounds.bottom + mLocationBarBackgroundNtpOffset.bottom
+ mLocationBarBackgroundPadding.bottom);
mLocationBarBackground.draw(canvas);
}
float locationBarClipLeft =
mLocationBarBackgroundBounds.left + mLocationBarBackgroundNtpOffset.left;
float locationBarClipRight =
mLocationBarBackgroundBounds.right + mLocationBarBackgroundNtpOffset.right;
float locationBarClipTop =
mLocationBarBackgroundBounds.top + mLocationBarBackgroundNtpOffset.top;
float locationBarClipBottom =
mLocationBarBackgroundBounds.bottom + mLocationBarBackgroundNtpOffset.bottom;
// When unexpanded, the location bar's visible content boundaries are inset from the
// viewport used to draw the background. During expansion transitions, compensation
// is applied to increase the clip regions such that when the location bar converts
// to the narrower collapsed layout that the visible content is the same.
if (mUrlExpansionPercent != 1f) {
int leftDelta = mUnfocusedLocationBarLayoutLeft
- getViewBoundsLeftOfLocationBar(mVisualState);
int rightDelta = getViewBoundsRightOfLocationBar(mVisualState)
- mUnfocusedLocationBarLayoutLeft
- mUnfocusedLocationBarLayoutWidth;
float inversePercent = 1f - mUrlExpansionPercent;
locationBarClipLeft += leftDelta * inversePercent;
locationBarClipRight -= rightDelta * inversePercent;
}
// Clip the location bar child to the URL viewport calculated in onDraw.
canvas.clipRect(
locationBarClipLeft, locationBarClipTop,
locationBarClipRight, locationBarClipBottom);
clipped = true;
}
boolean retVal = super.drawChild(canvas, mLocationBar, drawingTime);
if (clipped) canvas.restore();
return retVal;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mBackgroundOverlayBounds.set(0, 0, w, mToolbarHeightWithoutShadow);
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mToolbarShadow = (ImageView) getRootView().findViewById(R.id.toolbar_shadow);
// This is a workaround for http://crbug.com/574928. Since Jelly Bean is the lowest version
// we support now and the next deprecation target, we decided to simply workaround.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
mToolbarShadow.setImageDrawable(
ApiCompatibilityUtils.getDrawable(getResources(), R.drawable.toolbar_shadow));
}
}
@Override
public void draw(Canvas canvas) {
// If capturing a texture of the toolbar, ensure the alpha is set prior to draw(...) being
// called. The alpha is being used prior to getting to draw(...), so updating the value
// after this point was having no affect.
if (mTextureCaptureMode) assert getAlpha() == 1f;
// mClipRect can change in the draw call, so cache this value to ensure the canvas is
// restored correctly.
boolean shouldClip = !mTextureCaptureMode && mClipRect != null;
if (shouldClip) {
canvas.save();
canvas.clipRect(mClipRect);
}
super.draw(canvas);
if (shouldClip) {
canvas.restore();
// Post an invalidate when the clip rect becomes null to ensure another draw pass occurs
// and the full toolbar is drawn again.
if (mClipRect == null) postInvalidate();
}
}
@Override
public void onStateRestored() {
if (mToggleTabStackButton != null) mToggleTabStackButton.setClickable(true);
}
@Override
public boolean isReadyForTextureCapture() {
if (mForceTextureCapture) {
return true;
}
return !(mTabSwitcherState == TAB_SWITCHER || mTabSwitcherModeAnimation != null
|| urlHasFocus() || mUrlFocusChangeInProgress);
}
@Override
public boolean setForceTextureCapture(boolean forceTextureCapture) {
if (forceTextureCapture) {
setUseLightDrawablesForTextureCapture();
// Only force a texture capture if the tint for the toolbar drawables is changing.
mForceTextureCapture = mLightDrawablesUsedForLastTextureCapture
!= mUseLightDrawablesForTextureCapture;
return mForceTextureCapture;
}
mForceTextureCapture = forceTextureCapture;
return false;
}
@Override
public void setLayoutUpdateHost(LayoutUpdateHost layoutUpdateHost) {
mLayoutUpdateHost = layoutUpdateHost;
}
@Override
public void finishAnimations() {
mClipRect = null;
if (mTabSwitcherModeAnimation != null) {
mTabSwitcherModeAnimation.end();
mTabSwitcherModeAnimation = null;
}
if (mDelayedTabSwitcherModeAnimation != null) {
mDelayedTabSwitcherModeAnimation.end();
mDelayedTabSwitcherModeAnimation = null;
}
// The Android framework calls onAnimationEnd() on listeners before Animator#isRunning()
// returns false. Sometimes this causes the progress bar visibility to be set incorrectly.
// Update the visibility now that animations are set to null. (see crbug.com/606419)
updateProgressBarVisibility();
}
@Override
public void getLocationBarContentRect(Rect outRect) {
updateLocationBarBackgroundBounds(outRect, VisualState.NORMAL);
}
@Override
protected void onHomeButtonUpdate(boolean homeButtonEnabled) {
mIsHomeButtonEnabled = homeButtonEnabled;
updateButtonVisibility();
}
@Override
public void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
updateButtonVisibility();
}
@Override
public void updateButtonVisibility() {
if (mIsHomeButtonEnabled) {
mHomeButton.setVisibility(urlHasFocus() || isTabSwitcherAnimationRunning()
? INVISIBLE : VISIBLE);
mBrowsingModeViews.add(mHomeButton);
} else {
mHomeButton.setVisibility(GONE);
mBrowsingModeViews.remove(mHomeButton);
}
}
private ObjectAnimator createEnterTabSwitcherModeAnimation() {
ObjectAnimator enterAnimation =
ObjectAnimator.ofFloat(this, mTabSwitcherModePercentProperty, 1.f);
enterAnimation.setDuration(TAB_SWITCHER_MODE_ENTER_ANIMATION_DURATION_MS);
enterAnimation.setInterpolator(new LinearInterpolator());
enterAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// This is to deal with the view going invisible when resuming the activity and
// running this animation. The view is still there and clickable but does not
// render and only a layout triggers a refresh. See crbug.com/306890.
if (!mToggleTabStackButton.isEnabled()) requestLayout();
}
});
return enterAnimation;
}
private ObjectAnimator createExitTabSwitcherAnimation(
final boolean animateNormalToolbar) {
ObjectAnimator exitAnimation =
ObjectAnimator.ofFloat(this, mTabSwitcherModePercentProperty, 0.f);
exitAnimation.setDuration(animateNormalToolbar
? TAB_SWITCHER_MODE_EXIT_NORMAL_ANIMATION_DURATION_MS
: TAB_SWITCHER_MODE_EXIT_FADE_ANIMATION_DURATION_MS);
exitAnimation.setInterpolator(new LinearInterpolator());
exitAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
updateViewsForTabSwitcherMode();
}
});
return exitAnimation;
}
private ObjectAnimator createPostExitTabSwitcherAnimation() {
ObjectAnimator exitAnimation = ObjectAnimator.ofFloat(
this, View.TRANSLATION_Y, -getHeight(), 0.f);
exitAnimation.setDuration(TAB_SWITCHER_MODE_POST_EXIT_ANIMATION_DURATION_MS);
exitAnimation.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
exitAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
updateViewsForTabSwitcherMode();
// On older builds, force an update to ensure the new visuals are used
// when bringing in the toolbar. crbug.com/404571
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) {
requestLayout();
}
}
@Override
public void onAnimationEnd(Animator animation) {
mDelayedTabSwitcherModeAnimation = null;
updateShadowVisibility();
updateViewsForTabSwitcherMode();
}
});
return exitAnimation;
}
@Override
public void setTextureCaptureMode(boolean textureMode) {
assert mTextureCaptureMode != textureMode;
mTextureCaptureMode = textureMode;
if (mTextureCaptureMode) {
mToolbarShadow.setVisibility(VISIBLE);
mPreTextureCaptureAlpha = getAlpha();
setAlpha(1);
} else {
setAlpha(mPreTextureCaptureAlpha);
updateShadowVisibility();
mPreTextureCaptureAlpha = 1f;
}
}
private boolean isTabSwitcherAnimationRunning() {
return mTabSwitcherState == ENTERING_TAB_SWITCHER
|| mTabSwitcherState == EXITING_TAB_SWITCHER;
}
private void updateViewsForTabSwitcherMode() {
int tabSwitcherViewsVisibility = mTabSwitcherState != STATIC_TAB ? VISIBLE : INVISIBLE;
int browsingViewsVisibility = mTabSwitcherState != STATIC_TAB ? INVISIBLE : VISIBLE;
for (View view : mTabSwitcherModeViews) {
view.setVisibility(tabSwitcherViewsVisibility);
}
for (View view : mBrowsingModeViews) {
view.setVisibility(browsingViewsVisibility);
}
if (mShowMenuBadge) {
setMenuButtonContentDescription(mTabSwitcherState == STATIC_TAB);
}
updateProgressBarVisibility();
updateVisualsForToolbarState();
}
private void updateProgressBarVisibility() {
getProgressBar().setVisibility(mTabSwitcherState != STATIC_TAB ? INVISIBLE : VISIBLE);
}
@Override
protected void setContentAttached(boolean attached) {
updateVisualsForToolbarState();
}
@Override
protected void setTabSwitcherMode(
boolean inTabSwitcherMode, boolean showToolbar, boolean delayAnimation) {
// If setting tab switcher mode to true and the browser is already animating or in the tab
// switcher skip.
if (inTabSwitcherMode && (mTabSwitcherState == TAB_SWITCHER
|| mTabSwitcherState == ENTERING_TAB_SWITCHER)) {
return;
}
// Likewise if exiting the tab switcher.
if (!inTabSwitcherMode && (mTabSwitcherState == STATIC_TAB
|| mTabSwitcherState == EXITING_TAB_SWITCHER)) {
return;
}
mTabSwitcherState = inTabSwitcherMode ? ENTERING_TAB_SWITCHER : EXITING_TAB_SWITCHER;
mLocationBar.setUrlBarFocusable(false);
finishAnimations();
mDelayingTabSwitcherAnimation = delayAnimation;
if (inTabSwitcherMode) {
if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) {
mUrlFocusLayoutAnimator.end();
mUrlFocusLayoutAnimator = null;
// After finishing the animation, force a re-layout of the location bar,
// so that the final translation position is correct (since onMeasure updates
// won't happen in tab switcher mode). crbug.com/518795.
layoutLocationBar(getMeasuredWidth());
updateUrlExpansionAnimation();
}
mNewTabButton.setEnabled(true);
updateViewsForTabSwitcherMode();
mTabSwitcherModeAnimation = createEnterTabSwitcherModeAnimation();
} else {
if (!mDelayingTabSwitcherAnimation) {
mTabSwitcherModeAnimation = createExitTabSwitcherAnimation(showToolbar);
}
}
updateButtonsTranslationY();
mAnimateNormalToolbar = showToolbar;
if (mTabSwitcherModeAnimation != null) mTabSwitcherModeAnimation.start();
if (SysUtils.isLowEndDevice()) finishAnimations();
postInvalidateOnAnimation();
}
@Override
protected void onTabSwitcherTransitionFinished() {
setAlpha(1.f);
mClipRect = null;
// Detect what was being transitioned from and set the new state appropriately.
if (mTabSwitcherState == EXITING_TAB_SWITCHER) {
mLocationBar.setUrlBarFocusable(true);
mTabSwitcherState = STATIC_TAB;
}
if (mTabSwitcherState == ENTERING_TAB_SWITCHER) mTabSwitcherState = TAB_SWITCHER;
mTabSwitcherModePercent = mTabSwitcherState != STATIC_TAB ? 1.0f : 0.0f;
if (!mAnimateNormalToolbar) {
finishAnimations();
updateVisualsForToolbarState();
}
if (mDelayingTabSwitcherAnimation) {
mDelayingTabSwitcherAnimation = false;
mDelayedTabSwitcherModeAnimation = createPostExitTabSwitcherAnimation();
mDelayedTabSwitcherModeAnimation.start();
} else {
updateViewsForTabSwitcherMode();
}
}
private void updateOverlayDrawables() {
if (!isNativeLibraryReady()) return;
VisualState overlayState = computeVisualState(false);
boolean visualStateChanged = mOverlayDrawablesVisualState != overlayState;
if (!visualStateChanged && mVisualState == VisualState.BRAND_COLOR
&& getToolbarDataProvider().getPrimaryColor()
!= mTabSwitcherAnimationBgOverlay.getColor()) {
visualStateChanged = true;
}
if (!visualStateChanged) return;
mOverlayDrawablesVisualState = overlayState;
mTabSwitcherAnimationBgOverlay.setColor(getToolbarColorForVisualState(
mOverlayDrawablesVisualState));
setTabSwitcherAnimationMenuDrawable();
setUseLightDrawablesForTextureCapture();
if (mTabSwitcherState == STATIC_TAB && !mTextureCaptureMode && mLayoutUpdateHost != null) {
// Request a layout update to trigger a texture capture if the tint color is changing
// and we're not already in texture capture mode. This is necessary if the tab switcher
// is entered immediately after a change to the tint color without any user interactions
// that would normally trigger a texture capture.
mLayoutUpdateHost.requestUpdate();
}
}
@Override
public void destroy() {
dismissTabSwitcherCallout();
}
@Override
public void setOnTabSwitcherClickHandler(OnClickListener listener) {
mTabSwitcherListener = listener;
}
@Override
public void setOnNewTabClickHandler(OnClickListener listener) {
mNewTabListener = listener;
}
@Override
public boolean shouldIgnoreSwipeGesture() {
return super.shouldIgnoreSwipeGesture() || mUrlExpansionPercent > 0f
|| mNtpSearchBoxTranslation.y < 0f;
}
private Property<TextView, Integer> buildUrlScrollProperty(
final View containerView, final boolean isContainerRtl) {
// If the RTL-ness of the container view changes during an animation, the scroll values
// become invalid. If that happens, snap to the ending position and no longer update.
return new Property<TextView, Integer>(Integer.class, "scrollX") {
private boolean mRtlStateInvalid;
@Override
public Integer get(TextView view) {
return view.getScrollX();
}
@Override
public void set(TextView view, Integer scrollX) {
if (mRtlStateInvalid) return;
boolean rtl = ApiCompatibilityUtils.isLayoutRtl(containerView);
if (rtl != isContainerRtl) {
mRtlStateInvalid = true;
if (!rtl || mUrlBar.getLayout() != null) {
scrollX = 0;
if (rtl) {
scrollX = (int) view.getLayout().getPrimaryHorizontal(0);
scrollX -= view.getWidth();
}
}
}
view.setScrollX(scrollX);
}
};
}
private void populateUrlFocusingAnimatorSet(List<Animator> animators) {
Animator animator = ObjectAnimator.ofFloat(this, mUrlFocusChangePercentProperty, 1f);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
for (int i = 0; i < mLocationBar.getChildCount(); i++) {
View childView = mLocationBar.getChildAt(i);
if (childView == mLocationBar.getFirstViewVisibleWhenFocused()) break;
animator = ObjectAnimator.ofFloat(childView, ALPHA, 0);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
}
float density = getContext().getResources().getDisplayMetrics().density;
boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(this);
float toolbarButtonTranslationX = MathUtils.flipSignIf(
URL_FOCUS_TOOLBAR_BUTTONS_TRANSLATION_X_DP, isRtl) * density;
animator = ObjectAnimator.ofFloat(
mMenuButtonWrapper, TRANSLATION_X, toolbarButtonTranslationX);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
animators.add(animator);
animator = ObjectAnimator.ofFloat(mMenuButtonWrapper, ALPHA, 0);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
animators.add(animator);
if (mToggleTabStackButton != null) {
animator = ObjectAnimator.ofFloat(
mToggleTabStackButton, TRANSLATION_X, toolbarButtonTranslationX);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
animators.add(animator);
animator = ObjectAnimator.ofFloat(mToggleTabStackButton, ALPHA, 0);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE);
animators.add(animator);
}
}
private void populateUrlClearFocusingAnimatorSet(List<Animator> animators) {
Animator animator = ObjectAnimator.ofFloat(this, mUrlFocusChangePercentProperty, 0f);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
animator = ObjectAnimator.ofFloat(mMenuButtonWrapper, TRANSLATION_X, 0);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setStartDelay(URL_CLEAR_FOCUS_MENU_DELAY_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
animator = ObjectAnimator.ofFloat(mMenuButtonWrapper, ALPHA, 1);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setStartDelay(URL_CLEAR_FOCUS_MENU_DELAY_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
if (mToggleTabStackButton != null) {
animator = ObjectAnimator.ofFloat(mToggleTabStackButton, TRANSLATION_X, 0);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setStartDelay(URL_CLEAR_FOCUS_TABSTACK_DELAY_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
animator = ObjectAnimator.ofFloat(mToggleTabStackButton, ALPHA, 1);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setStartDelay(URL_CLEAR_FOCUS_TABSTACK_DELAY_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
}
for (int i = 0; i < mLocationBar.getChildCount(); i++) {
View childView = mLocationBar.getChildAt(i);
if (childView == mLocationBar.getFirstViewVisibleWhenFocused()) break;
animator = ObjectAnimator.ofFloat(childView, ALPHA, 1);
animator.setStartDelay(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setDuration(URL_CLEAR_FOCUS_MENU_DELAY_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
}
if (isLocationBarShownInNTP() && mNtpSearchBoxScrollPercent == 0f) return;
// The call to getLayout() can return null briefly during text changes, but as it
// is only needed for RTL calculations, we proceed if the location bar is showing
// LTR content.
boolean isLocationBarRtl = ApiCompatibilityUtils.isLayoutRtl(mLocationBar);
if (!isLocationBarRtl || mUrlBar.getLayout() != null) {
int urlBarStartScrollX = 0;
if (isLocationBarRtl) {
urlBarStartScrollX = (int) mUrlBar.getLayout().getPrimaryHorizontal(0);
urlBarStartScrollX -= mUrlBar.getWidth();
}
// If the scroll position matches the current scroll position, do not trigger
// this animation as it will cause visible jumps when going from cleared text
// back to page URLs (despite it continually calling setScrollX with the same
// number).
if (mUrlBar.getScrollX() != urlBarStartScrollX) {
animator = ObjectAnimator.ofInt(mUrlBar,
buildUrlScrollProperty(mLocationBar, isLocationBarRtl), urlBarStartScrollX);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
animators.add(animator);
}
}
}
@Override
public void onUrlFocusChange(final boolean hasFocus) {
super.onUrlFocusChange(hasFocus);
triggerUrlFocusAnimation(hasFocus);
TransitionDrawable shadowDrawable = (TransitionDrawable) mToolbarShadow.getDrawable();
if (hasFocus) {
dismissTabSwitcherCallout();
shadowDrawable.startTransition(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
} else {
shadowDrawable.reverseTransition(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
}
}
private void triggerUrlFocusAnimation(final boolean hasFocus) {
if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) {
mUrlFocusLayoutAnimator.cancel();
mUrlFocusLayoutAnimator = null;
}
List<Animator> animators = new ArrayList<>();
if (hasFocus) {
populateUrlFocusingAnimatorSet(animators);
} else {
populateUrlClearFocusingAnimatorSet(animators);
}
mUrlFocusLayoutAnimator = new AnimatorSet();
mUrlFocusLayoutAnimator.playTogether(animators);
mUrlFocusChangeInProgress = true;
mUrlFocusLayoutAnimator.addListener(new AnimatorListenerAdapter() {
private boolean mCanceled;
@Override
public void onAnimationStart(Animator animation) {
if (!hasFocus) {
mDisableLocationBarRelayout = true;
} else {
mLayoutLocationBarInFocusedMode = true;
requestLayout();
}
}
@Override
public void onAnimationCancel(Animator animation) {
mCanceled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
if (mCanceled) return;
if (!hasFocus) {
mDisableLocationBarRelayout = false;
mLayoutLocationBarInFocusedMode = false;
requestLayout();
}
mLocationBar.finishUrlFocusChange(hasFocus);
mUrlFocusChangeInProgress = false;
}
});
mUrlFocusLayoutAnimator.start();
}
@Override
protected void updateTabCountVisuals(int numberOfTabs) {
if (mHomeButton != null) mHomeButton.setEnabled(true);
if (mToggleTabStackButton == null) return;
mToggleTabStackButton.setEnabled(numberOfTabs >= 1);
mToggleTabStackButton.setContentDescription(
getResources().getQuantityString(
R.plurals.accessibility_toolbar_btn_tabswitcher_toggle,
numberOfTabs, numberOfTabs));
mTabSwitcherButtonDrawableLight.updateForTabCount(numberOfTabs, isIncognito());
mTabSwitcherButtonDrawable.updateForTabCount(numberOfTabs, isIncognito());
int themeColor;
if (getToolbarDataProvider() != null) {
themeColor = getToolbarDataProvider().getPrimaryColor();
} else {
themeColor = getToolbarColorForVisualState(
isIncognito() ? VisualState.INCOGNITO : VisualState.NORMAL);
}
boolean useTabStackDrawableLight = isIncognito()
|| ColorUtils.shouldUseLightForegroundOnBackground(themeColor);
if (mTabSwitcherAnimationTabStackDrawable == null
|| mIsOverlayTabStackDrawableLight != useTabStackDrawableLight) {
mTabSwitcherAnimationTabStackDrawable =
TabSwitcherDrawable.createTabSwitcherDrawable(
getResources(), useTabStackDrawableLight);
int[] stateSet = {android.R.attr.state_enabled};
mTabSwitcherAnimationTabStackDrawable.setState(stateSet);
mTabSwitcherAnimationTabStackDrawable.setBounds(
mToggleTabStackButton.getDrawable().getBounds());
mIsOverlayTabStackDrawableLight = useTabStackDrawableLight;
}
if (mTabSwitcherAnimationTabStackDrawable != null) {
mTabSwitcherAnimationTabStackDrawable.updateForTabCount(
numberOfTabs, isIncognito());
}
}
@Override
protected void onTabContentViewChanged() {
super.onTabContentViewChanged();
updateNtpAnimationState();
updateVisualsForToolbarState();
}
@Override
protected void onTabOrModelChanged() {
super.onTabOrModelChanged();
updateNtpAnimationState();
updateVisualsForToolbarState();
if (mHasCheckedIfTabSwitcherCalloutIsNecessary) {
dismissTabSwitcherCallout();
} else {
mHasCheckedIfTabSwitcherCalloutIsNecessary = true;
showTabSwitcherCalloutIfNecessary();
}
}
private static boolean isVisualStateValidForBrandColorTransition(VisualState state) {
return state == VisualState.NORMAL || state == VisualState.BRAND_COLOR;
}
@Override
protected void onPrimaryColorChanged(boolean shouldAnimate) {
super.onPrimaryColorChanged(shouldAnimate);
if (mBrandColorTransitionActive) mBrandColorTransitionAnimation.cancel();
final int initialColor = mToolbarBackground.getColor();
final int finalColor = getToolbarDataProvider().getPrimaryColor();
if (initialColor == finalColor) return;
if (!isVisualStateValidForBrandColorTransition(mVisualState)) return;
if (!shouldAnimate) {
updateToolbarBackground(finalColor);
return;
}
boolean shouldUseOpaque = ColorUtils.shouldUseOpaqueTextboxBackground(finalColor);
final int initialAlpha = mLocationBarBackgroundAlpha;
final int finalAlpha =
shouldUseOpaque ? 255 : LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA;
final boolean shouldAnimateAlpha = initialAlpha != finalAlpha;
mBrandColorTransitionAnimation = ValueAnimator.ofFloat(0, 1)
.setDuration(THEME_COLOR_TRANSITION_DURATION);
mBrandColorTransitionAnimation.setInterpolator(BakedBezierInterpolator.TRANSFORM_CURVE);
mBrandColorTransitionAnimation.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float fraction = animation.getAnimatedFraction();
if (shouldAnimateAlpha) {
mLocationBarBackgroundAlpha =
(int) (MathUtils.interpolate(initialAlpha, finalAlpha, fraction));
}
updateToolbarBackground(
ColorUtils.getColorWithOverlay(initialColor, finalColor, fraction));
}
});
mBrandColorTransitionAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mBrandColorTransitionActive = false;
updateVisualsForToolbarState();
}
});
mBrandColorTransitionAnimation.start();
mBrandColorTransitionActive = true;
}
private void updateNtpAnimationState() {
// Store previous NTP scroll before calling reset as that clears this value.
boolean wasShowingNtp = mVisibleNewTabPage != null;
float previousNtpScrollPercent = mNtpSearchBoxScrollPercent;
resetNtpAnimationValues();
if (mVisibleNewTabPage != null) {
mVisibleNewTabPage.setSearchBoxScrollListener(null);
mVisibleNewTabPage = null;
}
mVisibleNewTabPage = getToolbarDataProvider().getNewTabPageForCurrentTab();
if (mVisibleNewTabPage != null && mVisibleNewTabPage.isLocationBarShownInNTP()) {
mVisibleNewTabPage.setSearchBoxScrollListener(this);
requestLayout();
} else if (wasShowingNtp) {
// Convert the previous NTP scroll percentage to URL focus percentage because that
// will give a nicer transition animation from the expanded NTP omnibox to the
// collapsed normal omnibox on other non-NTP pages.
if (mTabSwitcherState == STATIC_TAB && previousNtpScrollPercent > 0f) {
mUrlFocusChangePercent =
Math.max(previousNtpScrollPercent, mUrlFocusChangePercent);
triggerUrlFocusAnimation(false);
}
requestLayout();
}
}
@Override
protected void onDefaultSearchEngineChanged() {
super.onDefaultSearchEngineChanged();
// Post an update for the toolbar state, which will allow all other listeners
// for the search engine change to update before we check on the state of the
// world for a UI update.
// TODO(tedchoc): Move away from updating based on the search engine change and instead
// add the toolbar as a listener to the NewTabPage and udpate only when
// it notifies the listeners that it has changed its state.
post(new Runnable() {
@Override
public void run() {
updateVisualsForToolbarState();
updateNtpAnimationState();
}
});
}
@Override
protected void handleFindToolbarStateChange(boolean showing) {
setVisibility(showing ? View.GONE : View.VISIBLE);
TransitionDrawable shadowDrawable = (TransitionDrawable) mToolbarShadow.getDrawable();
if (showing) {
shadowDrawable.startTransition(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
} else {
shadowDrawable.reverseTransition(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
}
}
private boolean isLocationBarShownInNTP() {
NewTabPage ntp = getToolbarDataProvider().getNewTabPageForCurrentTab();
return ntp != null && ntp.isLocationBarShownInNTP();
}
private void updateShadowVisibility() {
boolean shouldDrawShadow = mTabSwitcherState == STATIC_TAB;
int shadowVisibility = shouldDrawShadow ? View.VISIBLE : View.INVISIBLE;
if (mToolbarShadow.getVisibility() != shadowVisibility) {
mToolbarShadow.setVisibility(shadowVisibility);
}
}
private VisualState computeVisualState(boolean isInTabSwitcherMode) {
if (isInTabSwitcherMode && isIncognito()) return VisualState.TAB_SWITCHER_INCOGNITO;
if (isInTabSwitcherMode && !isIncognito()) return VisualState.TAB_SWITCHER_NORMAL;
if (isLocationBarShownInNTP()) return VisualState.NEW_TAB_NORMAL;
if (isIncognito()) return VisualState.INCOGNITO;
if (getToolbarDataProvider().isUsingBrandColor()) return VisualState.BRAND_COLOR;
return VisualState.NORMAL;
}
private void updateVisualsForToolbarState() {
final boolean isIncognito = isIncognito();
// These are important for setting visual state while the entering or leaving the tab
// switcher.
boolean inOrEnteringStaticTab = mTabSwitcherState == STATIC_TAB
|| mTabSwitcherState == EXITING_TAB_SWITCHER;
boolean inOrEnteringTabSwitcher = !inOrEnteringStaticTab;
VisualState newVisualState = computeVisualState(inOrEnteringTabSwitcher);
// If we are navigating to or from a brand color, allow the transition animation
// to run to completion as it will handle the triggering this path again and committing
// the proper visual state when it finishes. Brand color transitions are only valid
// between normal non-incognito pages and brand color pages, so if the visual states
// do not match then cancel the animation below.
if (mBrandColorTransitionActive
&& isVisualStateValidForBrandColorTransition(mVisualState)
&& isVisualStateValidForBrandColorTransition(newVisualState)) {
return;
} else if (mBrandColorTransitionAnimation != null
&& mBrandColorTransitionAnimation.isRunning()) {
mBrandColorTransitionAnimation.cancel();
}
boolean visualStateChanged = mVisualState != newVisualState;
int currentPrimaryColor = getToolbarDataProvider().getPrimaryColor();
int themeColorForProgressBar = currentPrimaryColor;
// If The page is native force the use of the standard theme for the progress bar.
if (getToolbarDataProvider() != null && getToolbarDataProvider().getTab() != null
&& getToolbarDataProvider().getTab().isNativePage()) {
VisualState visualState = isIncognito() ? VisualState.INCOGNITO : VisualState.NORMAL;
themeColorForProgressBar = getToolbarColorForVisualState(visualState);
}
if (mVisualState == VisualState.BRAND_COLOR && !visualStateChanged) {
boolean useLightToolbarDrawables =
ColorUtils.shouldUseLightForegroundOnBackground(currentPrimaryColor);
boolean unfocusedLocationBarUsesTransparentBg =
!ColorUtils.shouldUseOpaqueTextboxBackground(currentPrimaryColor);
if (useLightToolbarDrawables != mUseLightToolbarDrawables
|| unfocusedLocationBarUsesTransparentBg
!= mUnfocusedLocationBarUsesTransparentBg) {
visualStateChanged = true;
} else {
updateToolbarBackground(VisualState.BRAND_COLOR);
getProgressBar().setThemeColor(themeColorForProgressBar, isIncognito());
}
}
mVisualState = newVisualState;
updateOverlayDrawables();
updateShadowVisibility();
updateUrlExpansionAnimation();
if (!visualStateChanged) {
if (mVisualState == VisualState.NEW_TAB_NORMAL) {
updateNtpTransitionAnimation();
} else {
resetNtpAnimationValues();
}
return;
}
mUseLightToolbarDrawables = false;
mUnfocusedLocationBarUsesTransparentBg = false;
mLocationBarBackgroundAlpha = 255;
updateToolbarBackground(mVisualState);
getProgressBar().setThemeColor(themeColorForProgressBar, isIncognito());
if (inOrEnteringTabSwitcher) {
mUseLightToolbarDrawables = true;
mLocationBarBackgroundAlpha = LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA;
getProgressBar().setBackgroundColor(mProgressBackBackgroundColorWhite);
getProgressBar().setForegroundColor(ApiCompatibilityUtils.getColor(getResources(),
R.color.progress_bar_foreground_white));
} else if (isIncognito()) {
mUseLightToolbarDrawables = true;
mLocationBarBackgroundAlpha = LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA;
} else if (mVisualState == VisualState.BRAND_COLOR) {
mUseLightToolbarDrawables =
ColorUtils.shouldUseLightForegroundOnBackground(currentPrimaryColor);
mUnfocusedLocationBarUsesTransparentBg =
!ColorUtils.shouldUseOpaqueTextboxBackground(currentPrimaryColor);
mLocationBarBackgroundAlpha = mUnfocusedLocationBarUsesTransparentBg
? LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA
: 255;
}
if (mToggleTabStackButton != null) {
mToggleTabStackButton.setImageDrawable(mUseLightToolbarDrawables
? mTabSwitcherButtonDrawableLight : mTabSwitcherButtonDrawable);
if (mTabSwitcherAnimationTabStackDrawable != null) {
mTabSwitcherAnimationTabStackDrawable.setTint(
mUseLightToolbarDrawables ? mLightModeTint : mDarkModeTint);
}
}
mMenuButton.setTint(mUseLightToolbarDrawables ? mLightModeTint : mDarkModeTint);
if (mShowMenuBadge && inOrEnteringStaticTab) {
setAppMenuUpdateBadgeDrawable(mUseLightToolbarDrawables);
}
ColorStateList tint = mUseLightToolbarDrawables ? mLightModeTint : mDarkModeTint;
if (mIsHomeButtonEnabled) mHomeButton.setTint(tint);
mLocationBar.updateVisualsForState();
// Remove the side padding for incognito to ensure the badge icon aligns correctly with the
// background of the location bar.
if (isIncognito) {
mLocationBar.setPadding(
0, mLocationBarBackgroundPadding.top, 0, mLocationBarBackgroundPadding.bottom);
} else {
mLocationBar.setPadding(
mLocationBarBackgroundPadding.left, mLocationBarBackgroundPadding.top,
mLocationBarBackgroundPadding.right, mLocationBarBackgroundPadding.bottom);
}
// We update the alpha before comparing the visual state as we need to change
// its value when entering and exiting TabSwitcher mode.
if (isLocationBarShownInNTP() && inOrEnteringStaticTab) {
updateNtpTransitionAnimation();
}
mNewTabButton.setIsIncognito(isIncognito);
CharSequence newTabContentDescription = getResources().getText(
isIncognito ? R.string.accessibility_toolbar_btn_new_incognito_tab :
R.string.accessibility_toolbar_btn_new_tab);
if (mNewTabButton != null
&& !newTabContentDescription.equals(mNewTabButton.getContentDescription())) {
mNewTabButton.setContentDescription(newTabContentDescription);
}
getMenuButtonWrapper().setVisibility(View.VISIBLE);
}
@Override
public LocationBar getLocationBar() {
return mLocationBar;
}
@Override
public void showAppMenuUpdateBadge() {
super.showAppMenuUpdateBadge();
// Set up variables.
if (!mBrowsingModeViews.contains(mMenuBadge)) {
mBrowsingModeViews.add(mMenuBadge);
}
// Finish any in-progress animations and set the TabSwitcherAnimationMenuBadgeDrawables.
finishAnimations();
setTabSwitcherAnimationMenuBadgeDrawable();
// Show the badge.
if (mTabSwitcherState == STATIC_TAB) {
if (mUseLightToolbarDrawables) {
setAppMenuUpdateBadgeDrawable(mUseLightToolbarDrawables);
}
setAppMenuUpdateBadgeToVisible(true);
}
}
@Override
public void removeAppMenuUpdateBadge(boolean animate) {
super.removeAppMenuUpdateBadge(animate);
if (mBrowsingModeViews.contains(mMenuBadge)) {
mBrowsingModeViews.remove(mMenuBadge);
mTabSwitcherAnimationMenuBadgeDarkDrawable = null;
mTabSwitcherAnimationMenuBadgeLightDrawable = null;
}
mLocationBar.removeAppMenuUpdateBadge(animate);
}
private void setTabSwitcherAnimationMenuDrawable() {
mTabSwitcherAnimationMenuDrawable = ApiCompatibilityUtils.getDrawable(getResources(),
R.drawable.btn_menu);
mTabSwitcherAnimationMenuDrawable.mutate();
mTabSwitcherAnimationMenuDrawable.setColorFilter(
isIncognito() ? mLightModeDefaultColor : mDarkModeDefaultColor,
PorterDuff.Mode.SRC_IN);
((BitmapDrawable) mTabSwitcherAnimationMenuDrawable).setGravity(Gravity.CENTER);
}
private void setTabSwitcherAnimationMenuBadgeDrawable() {
mTabSwitcherAnimationMenuBadgeDarkDrawable = ApiCompatibilityUtils.getDrawable(
getResources(), R.drawable.badge_update_dark);
mTabSwitcherAnimationMenuBadgeDarkDrawable.mutate();
((BitmapDrawable) mTabSwitcherAnimationMenuBadgeDarkDrawable).setGravity(Gravity.CENTER);
mTabSwitcherAnimationMenuBadgeLightDrawable = ApiCompatibilityUtils.getDrawable(
getResources(), R.drawable.badge_update_light);
mTabSwitcherAnimationMenuBadgeLightDrawable.mutate();
((BitmapDrawable) mTabSwitcherAnimationMenuBadgeLightDrawable).setGravity(Gravity.CENTER);
}
@Override
public void setFullscreenManager(FullscreenManager manager) {
super.setFullscreenManager(manager);
mFullscreenManager = manager;
}
private void setUseLightDrawablesForTextureCapture() {
int currentPrimaryColor = getToolbarDataProvider().getPrimaryColor();
mUseLightDrawablesForTextureCapture =
isIncognito()
|| (currentPrimaryColor != 0
&& ColorUtils.shouldUseLightForegroundOnBackground(currentPrimaryColor));
}
private void dismissTabSwitcherCallout() {
if (mTabSwitcherCallout != null) mTabSwitcherCallout.dismiss();
}
private void showTabSwitcherCalloutIfNecessary() {
assert mTabSwitcherCallout == null;
mTabSwitcherCallout =
TabSwitcherCallout.showIfNecessary(getContext(), mToggleTabStackButton);
if (mTabSwitcherCallout == null) return;
mTabSwitcherCallout.setOnDismissListener(new OnDismissListener() {
@Override
public void onDismiss() {
if (mFullscreenManager != null) {
mFullscreenManager.hideControlsPersistent(mFullscreenCalloutToken);
mFullscreenCalloutToken = FullscreenManager.INVALID_TOKEN;
}
mTabSwitcherCallout = null;
}
});
if (mFullscreenManager != null) {
mFullscreenCalloutToken =
mFullscreenManager.showControlsPersistentAndClearOldToken(
mFullscreenCalloutToken);
}
}
}