// Copyright 2015 The Chromium Authors
// 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.top;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
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.Rect;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Handler;
import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.graphics.drawable.DrawableWrapperCompat;
import androidx.core.content.res.ResourcesCompat;
import androidx.core.widget.ImageViewCompat;
import org.chromium.base.Callback;
import org.chromium.base.MathUtils;
import org.chromium.base.TraceEvent;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.omnibox.LocationBarCoordinator;
import org.chromium.chrome.browser.omnibox.NewTabPageDelegate;
import org.chromium.chrome.browser.omnibox.SearchEngineUtils;
import org.chromium.chrome.browser.omnibox.UrlBarData;
import org.chromium.chrome.browser.omnibox.status.StatusCoordinator;
import org.chromium.chrome.browser.omnibox.styles.OmniboxResourceProvider;
import org.chromium.chrome.browser.omnibox.suggestions.OmniboxSuggestionsDropdownScrollListener;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.theme.ThemeUtils;
import org.chromium.chrome.browser.toolbar.ButtonData;
import org.chromium.chrome.browser.toolbar.KeyboardNavigationListener;
import org.chromium.chrome.browser.toolbar.R;
import org.chromium.chrome.browser.toolbar.ToolbarDataProvider;
import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
import org.chromium.chrome.browser.toolbar.ToolbarTabController;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator;
import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator;
import org.chromium.chrome.browser.toolbar.optional_button.OptionalButtonCoordinator.TransitionType;
import org.chromium.chrome.browser.toolbar.top.CaptureReadinessResult.TopToolbarBlockCaptureReason;
import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.ToolbarColorObserver;
import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator.UrlExpansionObserver;
import org.chromium.chrome.browser.ui.theme.BrandedColorScheme;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
import org.chromium.components.embedder_support.util.UrlUtilities;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.omnibox.OmniboxFeatures;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.base.ViewUtils;
import org.chromium.ui.interpolators.Interpolators;
import org.chromium.ui.util.ColorUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BooleanSupplier;
/** Phone specific toolbar implementation. */
public class ToolbarPhone extends ToolbarLayout
implements OnClickListener, OmniboxSuggestionsDropdownScrollListener {
/** 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 = 225;
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;
// Values used during animation to show/hide optional toolbar button.
private static final float UNINITIALIZED_FRACTION = -1f;
/** States that the toolbar can be in regarding the tab switcher. */
protected static final int STATIC_TAB = 0;
protected static final int TAB_SWITCHER = 1;
protected static final int ENTERING_TAB_SWITCHER = 2;
protected static final int EXITING_TAB_SWITCHER = 3;
// Finch params and default values for code cleanup.
private static final String PARAM_REMOVE_REDUNDANT_ANIM_CALL =
"remove_redundant_ntpupdate_in_lbvisualupdate";
public static final boolean PARAM_REMOVE_REDUNDANT_ANIM_CALL_DEFAULT_VAL = false;
@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")
})
private final Callback<Integer> mTabCountSupplierObserver;
private @Nullable ObservableSupplier<Integer> mTabCountSupplier;
private UserEducationHelper mUserEducationHelper;
protected LocationBarCoordinator mLocationBar;
private ObservableSupplier<Tracker> mTrackerSupplier;
private ViewGroup mToolbarButtonsContainer;
// Non-null after inflation occurs.
protected @NonNull ImageView mHomeButton;
private TextView mUrlBar;
protected View mUrlActionContainer;
private OptionalButtonCoordinator mOptionalButtonCoordinator;
@ViewDebug.ExportedProperty(category = "chrome")
protected 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")
protected boolean mTextureCaptureMode;
private boolean mForceTextureCapture;
@ViewDebug.ExportedProperty(category = "chrome")
protected boolean mUrlFocusChangeInProgress;
/** 1.0 is 100% focused, 0 is completely unfocused */
@ViewDebug.ExportedProperty(category = "chrome")
private float mUrlFocusChangeFraction;
/**
* 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 #mUrlFocusChangeFraction} and {@link #mNtpSearchBoxScrollFraction}.
*
* 0.0 == no expansion, 1.0 == fully expanded.
*/
@ViewDebug.ExportedProperty(category = "chrome")
protected float mUrlExpansionFraction;
private AnimatorSet mUrlFocusLayoutAnimator;
protected boolean mDisableLocationBarRelayout;
protected boolean mLayoutLocationBarInFocusedMode;
private boolean mLayoutLocationBarWithoutExtraButton;
protected int mUnfocusedLocationBarLayoutWidth;
protected int mUnfocusedLocationBarLayoutLeft;
protected int mUnfocusedLocationBarLayoutRight;
private boolean mUnfocusedLocationBarUsesTransparentBg;
private float mNtpSearchBoxScrollFraction = UNINITIALIZED_FRACTION;
protected ColorDrawable mToolbarBackground;
/** The omnibox background (white with a shadow). */
private GradientDrawable mLocationBarBackground;
private Drawable mActiveLocationBarBackground;
protected boolean mForceDrawLocationBarBackground;
/** The boundaries of the omnibox, without the NTP-specific offset applied. */
protected final Rect mLocationBarBackgroundBounds = 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;
/**
* Offset applied to the URL actions container due to the end padding of the fake search box on
* NTP.
*/
private float mUrlActionsNtpEndOffset;
private final Rect mNtpSearchBoxBounds = new Rect();
protected final Point mNtpSearchBoxTranslation = new Point();
private final int mToolbarSidePadding;
private final int mToolbarSidePaddingForNtp;
private final int mBackgroundHeightIncreaseWhenFocus;
private ValueAnimator mBrandColorTransitionAnimation;
private boolean mBrandColorTransitionActive;
private boolean mIsHomeButtonEnabled;
private Runnable mLayoutUpdater;
/** The vertical inset of the location bar background. */
private int mLocationBarBackgroundVerticalInset;
/** The current color of the location bar. */
private @ColorInt int mCurrentLocationBarColor;
private PhoneCaptureStateToken mPhoneCaptureStateToken;
private ButtonData mButtonData;
private @ColorInt int mToolbarBackgroundColorForNtp;
private @ColorInt int mLocationBarBackgroundColorForNtp;
/** Used to specify the visual state of the toolbar. */
@IntDef({
VisualState.NORMAL,
VisualState.INCOGNITO,
VisualState.BRAND_COLOR,
VisualState.NEW_TAB_NORMAL,
VisualState.NEW_TAB_SEARCH_ENGINE_NO_LOGO
})
@Retention(RetentionPolicy.SOURCE)
@interface VisualState {
int NORMAL = 0;
int INCOGNITO = 1;
int BRAND_COLOR = 2;
int NEW_TAB_NORMAL = 3;
int NEW_TAB_SEARCH_ENGINE_NO_LOGO = 4;
}
protected @VisualState int mVisualState = VisualState.NORMAL;
private float mPreTextureCaptureAlpha = 1f;
private int mPreTextureCaptureVisibility;
private @BrandedColorScheme int mOverlayTabStackDrawableScheme;
private boolean mOptionalButtonAnimationRunning;
private int mUrlFocusTranslationX;
private boolean mDropdownListScrolled;
// If we're in a layout transition, between startHiding to doneShowing.
private boolean mInLayoutTransition;
// For NTP, we have a different appearance (G logo background, search text color and style) of
// the real search box after it's pinned at top when scrolling up the surface. This variable
// distinguishes whether the current page is NTP with unfocused real omnibox, to indicate that
// the appearance of the real search box changed.
private boolean mIsNtpWithUnfocusedRealOmnibox;
// Added due to https://crbug.com/323888159 to mark the loading phase while navigating from NTP
// to webpages.
private boolean mIsInLoadingPhaseFromNtpToWebpage;
// 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 FloatProperty<ToolbarPhone> mUrlFocusChangeFractionProperty =
new FloatProperty<>("") {
@Override
public Float get(ToolbarPhone object) {
return object.mUrlFocusChangeFraction;
}
@Override
public void setValue(ToolbarPhone object, float value) {
setUrlFocusChangeFraction(value);
}
};
/**
* 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 = OmniboxResourceProvider.getToolbarSidePadding(context);
mToolbarSidePaddingForNtp = OmniboxResourceProvider.getToolbarSidePaddingForNtp(context);
mBackgroundHeightIncreaseWhenFocus =
OmniboxResourceProvider.getToolbarOnFocusHeightIncrease(context);
mToolbarBackgroundColorForNtp =
ChromeColors.getSurfaceColor(
getContext(), R.dimen.home_surface_background_color_elevation);
float LocationBarBackgroundColorAlphaForNtp =
ResourcesCompat.getFloat(
getResources(), R.dimen.home_surface_search_box_background_alpha);
mLocationBarBackgroundColorForNtp =
ColorUtils.setAlphaComponentWithFloat(
SemanticColorUtils.getDefaultIconColorAccent1(context),
LocationBarBackgroundColorAlphaForNtp);
mTabCountSupplierObserver = this::onTabCountChanged;
}
@Override
public void onFinishInflate() {
try (TraceEvent te = TraceEvent.scoped("ToolbarPhone.onFinishInflate")) {
super.onFinishInflate();
mToolbarButtonsContainer = findViewById(R.id.toolbar_buttons);
mHomeButton = findViewById(R.id.home_button);
mUrlBar = findViewById(R.id.url_bar);
mUrlActionContainer = findViewById(R.id.url_action_container);
mToolbarBackground =
new ColorDrawable(getToolbarColorForVisualState(VisualState.NORMAL));
setLayoutTransition(null);
if (getMenuButtonCoordinator() != null) {
getMenuButtonCoordinator().setVisibility(true);
}
setWillNotDraw(false);
mUrlFocusTranslationX =
getResources().getDimensionPixelSize(R.dimen.toolbar_url_focus_translation_x);
// Set hover tooltip texts for toolbar buttons shared between phones and tablets.
super.setTooltipTextForToolbarButtons();
}
}
@Override
public void initialize(
ToolbarDataProvider toolbarDataProvider,
ToolbarTabController tabController,
MenuButtonCoordinator menuButtonCoordinator,
ToggleTabStackButtonCoordinator tabSwitcherButtonCoordinator,
NavigationPopup.HistoryDelegate historyDelegate,
BooleanSupplier partnerHomepageEnabledSupplier,
ToolbarTablet.OfflineDownloader offlineDownloader,
UserEducationHelper userEducationHelper,
ObservableSupplier<Tracker> trackerSupplier) {
super.initialize(
toolbarDataProvider,
tabController,
menuButtonCoordinator,
tabSwitcherButtonCoordinator,
historyDelegate,
partnerHomepageEnabledSupplier,
offlineDownloader,
userEducationHelper,
trackerSupplier);
mUserEducationHelper = userEducationHelper;
mTrackerSupplier = trackerSupplier;
}
@Override
public void setLocationBarCoordinator(LocationBarCoordinator locationBarCoordinator) {
mLocationBar = locationBarCoordinator;
initLocationBarBackground();
}
@Override
public void destroy() {
cancelAnimations();
Handler handler = getHandler();
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
if (mTabCountSupplier != null) {
mTabCountSupplier.removeObserver(mTabCountSupplierObserver);
}
super.destroy();
}
/** Initializes the background, padding, margins, etc. for the location bar background. */
private void initLocationBarBackground() {
Resources res = getResources();
mLocationBarBackgroundVerticalInset =
res.getDimensionPixelSize(R.dimen.location_bar_vertical_margin);
mLocationBarBackground = createModernLocationBarBackground(getContext());
mActiveLocationBarBackground = mLocationBarBackground;
}
/**
* @param context The activity {@link Context}.
* @return The drawable for the modern location bar background.
*/
public static GradientDrawable createModernLocationBarBackground(Context context) {
GradientDrawable drawable =
(GradientDrawable)
context.getDrawable(
R.drawable.modern_toolbar_text_box_background_with_primary_color);
drawable.mutate();
drawable.setTint(ChromeColors.getSurfaceColor(context, R.dimen.toolbar_text_box_elevation));
return drawable;
}
/** Set the background color of the location bar to appropriately match the theme color. */
private void updateModernLocationBarColor(@ColorInt int color) {
if (mCurrentLocationBarColor == color) return;
mCurrentLocationBarColor = color;
mLocationBarBackground.setTint(color);
if (mOptionalButtonCoordinator != null) {
mOptionalButtonCoordinator.setBackgroundColorFilter(color);
}
}
private void updateModernLocationBarCorners() {
int nonFocusedRadius =
getResources()
.getDimensionPixelSize(R.dimen.modern_toolbar_background_corner_radius);
int focusedRadius =
getResources()
.getDimensionPixelSize(R.dimen.omnibox_suggestion_bg_round_corner_radius);
int radius =
(int)
MathUtils.interpolate(
nonFocusedRadius, focusedRadius, mUrlFocusChangeFraction);
mLocationBarBackground.setCornerRadius(radius);
}
/**
* Get the corresponding location bar color for a toolbar color.
*
* @param toolbarColor The color of the toolbar.
* @return The location bar color.
*/
private @ColorInt int getLocationBarColorForToolbarColor(@ColorInt int toolbarColor) {
if (isLocationBarShownInGeneralNtp() || mIsInLoadingPhaseFromNtpToWebpage) {
return mLocationBarBackgroundColorForNtp;
}
return ThemeUtils.getTextBoxColorForToolbarBackgroundInNonNativePage(
getContext(), toolbarColor, isIncognitoBranded(), /* isCustomTab= */ false);
}
/**
* Get the toolbar default color depending on the toolbar's status.
*
* @param shouldUseFocusColor True if should return the color for focus state.
*/
private @ColorInt int getToolbarDefaultColor(boolean shouldUseFocusColor) {
if (mLocationBar.getPhoneCoordinator().hasFocus() || shouldUseFocusColor) {
if (mDropdownListScrolled) {
return isIncognitoBranded()
? getContext().getColor(R.color.default_bg_color_dark_elev_2_baseline)
: ChromeColors.getSurfaceColor(
getContext(), R.dimen.toolbar_text_box_elevation);
}
return mLocationBar.getDropdownBackgroundColor(isIncognitoBranded());
}
return ChromeColors.getDefaultThemeColor(getContext(), isIncognitoBranded());
}
/**
* Get the corresponding default location bar color for a toolbar color. If location bar has
* focus, return the Omnibox suggestion background color. If location bar does not have focus,
* return {@link getLocationBarColorForToolbarColor(int toolbarColor)}.
*
* @param toolbarColor The color of the toolbar.
* @param shouldUseFocusColor True if should return the color for focus state.
* @return The default location bar color.
*/
private @ColorInt int getLocationBarDefaultColorForToolbarColor(
@ColorInt int toolbarColor, boolean shouldUseFocusColor) {
if (mLocationBar.getPhoneCoordinator().hasFocus() || shouldUseFocusColor) {
// Omnibox has same background as the Omnibox suggestion.
return mLocationBar.getSuggestionBackgroundColor(isIncognitoBranded());
}
return getLocationBarColorForToolbarColor(toolbarColor);
}
/** Sets up click and key listeners once we have native library available to handle clicks. */
@Override
protected void onNativeLibraryReady() {
super.onNativeLibraryReady();
mHomeButton.setOnClickListener(this);
getTabSwitcherButtonCoordinator()
.getContainerView()
.setOnKeyListener(
new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
if (isMenuButtonPresent()) {
return getMenuButtonCoordinator().getMenuButton();
} else {
return getCurrentTabView();
}
}
@Override
public View getNextFocusBackward() {
return findViewById(R.id.url_bar);
}
});
getMenuButtonCoordinator()
.setOnKeyListener(
new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
return getCurrentTabView();
}
@Override
public View getNextFocusBackward() {
return getTabSwitcherButtonCoordinator().getContainerView();
}
@Override
protected boolean handleEnterKeyPress() {
return getMenuButtonCoordinator().onEnterKeyPress();
}
});
updateVisualsForLocationBarState();
}
@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 (mNtpSearchBoxScrollFraction != 0f
&& mNtpSearchBoxScrollFraction != 1f
&& mNtpSearchBoxScrollFraction != UNINITIALIZED_FRACTION) {
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.getPhoneCoordinator().getTranslationY() > 0) {
return getToolbarDataProvider().getNewTabPageDelegate().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.getPhoneCoordinator().hasFocus()) {
return;
}
if (mHomeButton == v) {
recordHomeModuleClickedIfNTPVisible();
openHomepage();
if (mTrackerSupplier.hasValue() && mPartnerHomepageEnabledSupplier.getAsBoolean()) {
mTrackerSupplier.get().notifyEvent(EventConstants.PARTNER_HOME_PAGE_BUTTON_PRESSED);
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// In case the call came from the handler after we destroyed the dependencies, skip the work
// that could touch the already destroyed objects.
if (mDestroyChecker.isDestroyed()) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
return;
}
if (!mDisableLocationBarRelayout) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
boolean changed =
layoutLocationBarWithoutAnimationExpansion(
MeasureSpec.getSize(widthMeasureSpec));
updateUrlExpansionAnimation();
if (!changed) return;
} else {
updateUnfocusedLocationBarLayoutParams();
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
/**
* @return True if layout bar's unfocused width has changed, potentially causing updates to
* visual elements. If this happens during measurement pass, then toolbar's layout needs
* to be remeasured.
*/
private boolean updateUnfocusedLocationBarLayoutParams() {
int leftViewBounds = getViewBoundsLeftOfLocationBar(mVisualState);
int rightViewBounds = getViewBoundsRightOfLocationBar(mVisualState);
mUnfocusedLocationBarLayoutLeft = leftViewBounds;
mUnfocusedLocationBarLayoutRight = rightViewBounds;
int unfocusedLocationBarLayoutWidth = rightViewBounds - leftViewBounds;
if (mUnfocusedLocationBarLayoutWidth != unfocusedLocationBarLayoutWidth) {
mUnfocusedLocationBarLayoutWidth = unfocusedLocationBarLayoutWidth;
mLocationBar.setUnfocusedWidth(mUnfocusedLocationBarLayoutWidth);
return true;
}
return false;
}
/**
* @return The background drawable for the toolbar view.
*/
@VisibleForTesting
public ColorDrawable getBackgroundDrawable() {
return mToolbarBackground;
}
void setLocationBarBackgroundDrawableForTesting(GradientDrawable background) {
mLocationBarBackground = background;
}
void setOptionalButtonCoordinatorForTesting(
OptionalButtonCoordinator optionalButtonCoordinator) {
mOptionalButtonCoordinator = optionalButtonCoordinator;
}
@SuppressLint("RtlHardcoded")
private boolean layoutLocationBar(int containerWidth) {
TraceEvent.begin("ToolbarPhone.layoutLocationBar");
boolean changed = layoutLocationBarWithoutAnimationExpansion(containerWidth);
if (changed) updateLocationBarLayoutForExpansionAnimation();
TraceEvent.end("ToolbarPhone.layoutLocationBar");
return changed;
}
@SuppressLint("RtlHardcoded")
private boolean layoutLocationBarWithoutAnimationExpansion(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.
boolean changed = updateUnfocusedLocationBarLayoutParams();
if (mLayoutLocationBarInFocusedMode
|| (mVisualState == VisualState.NEW_TAB_NORMAL
&& mTabSwitcherState == STATIC_TAB)) {
int priorVisibleWidth =
mLocationBar.getPhoneCoordinator().getOffsetOfFirstVisibleFocusedView();
width =
getFocusedLocationBarWidth(
containerWidth, priorVisibleWidth, mLayoutLocationBarInFocusedMode);
leftMargin =
getFocusedLocationBarLeftMargin(
priorVisibleWidth, mLayoutLocationBarInFocusedMode);
} else {
width = mUnfocusedLocationBarLayoutWidth;
leftMargin = mUnfocusedLocationBarLayoutLeft;
}
if (mLayoutLocationBarWithoutExtraButton) {
float offset = getLocationBarWidthOffsetForOptionalButton();
if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) leftMargin -= (int) offset;
width += (int) offset;
}
changed |= (width != locationBarLayoutParams.width);
locationBarLayoutParams.width = width;
changed |= (leftMargin != locationBarLayoutParams.leftMargin);
locationBarLayoutParams.leftMargin = leftMargin;
return changed;
}
/**
* @param containerWidth The width of the view containing the location bar.
* @param priorVisibleWidth The width of any visible views prior to the location bar.
* @param isInFocusedMode Whether the current location bar is in focused mode.
* @return The width of the location bar when it has focus.
*/
private int getFocusedLocationBarWidth(
int containerWidth, int priorVisibleWidth, boolean isInFocusedMode) {
int width =
containerWidth
- (2 * (isInFocusedMode ? mToolbarSidePadding : mToolbarSidePaddingForNtp))
+ priorVisibleWidth;
return width;
}
/**
* @param priorVisibleWidth The width of any visible views prior to the location bar.
* @param isInFocusedMode Whether the current location bar is in focused mode.
* @return The left margin of the location bar when it has focus.
*/
private int getFocusedLocationBarLeftMargin(int priorVisibleWidth, boolean isInFocusedMode) {
int baseMargin = (isInFocusedMode ? mToolbarSidePadding : mToolbarSidePaddingForNtp);
if (mLocationBar.getPhoneCoordinator().getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
return baseMargin;
} else {
return baseMargin - priorVisibleWidth;
}
}
/**
* @param visualState The current {@link VisualState} of the toolbar.
* @return The left bounds of the location bar, accounting for any buttons on the left side
* of the toolbar.
*/
private int getViewBoundsLeftOfLocationBar(@VisualState int 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 && mTabSwitcherState == STATIC_TAB) {
return mToolbarSidePaddingForNtp;
} else if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
return getBoundsAfterAccountingForRightButtons();
} else {
return getBoundsAfterAccountingForLeftButton();
}
}
/**
* @return The left bounds of the location bar after accounting for any visible left buttons.
*/
private int getBoundsAfterAccountingForLeftButton() {
int padding = mToolbarSidePaddingForNtp;
// If home button is visible, mHomeButton.getMeasuredWidth() should be returned as the left
// bound.
if (mHomeButton.getVisibility() != GONE) {
padding = mHomeButton.getMeasuredWidth();
}
return padding;
}
/**
* @param visualState The current {@link VisualState} of the toolbar.
* @return The right bounds of the location bar, accounting for any buttons on the right side
* of the toolbar.
*/
private int getViewBoundsRightOfLocationBar(@VisualState int 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 && mTabSwitcherState == STATIC_TAB) {
return getMeasuredWidth() - mToolbarSidePaddingForNtp;
} else if (getLayoutDirection() == LAYOUT_DIRECTION_RTL) {
return getMeasuredWidth() - getBoundsAfterAccountingForLeftButton();
} else {
return getMeasuredWidth() - getBoundsAfterAccountingForRightButtons();
}
}
/**
* @return The right bounds of the location bar after accounting for any visible right buttons.
*/
private int getBoundsAfterAccountingForRightButtons() {
int toolbarButtonsContainerWidth = mToolbarButtonsContainer.getMeasuredWidth();
// MeasuredWidth() represents the desired width of the container which is accurate most
// time, except during the optional button animations, where the MeasuredWidth changes
// instantly to the final size and Width() represents the actual size at that frame.
if (mOptionalButtonAnimationRunning) {
toolbarButtonsContainerWidth = mToolbarButtonsContainer.getWidth();
}
return Math.max(mToolbarSidePaddingForNtp, toolbarButtonsContainerWidth);
}
private void updateToolbarBackground(@ColorInt int color) {
if (mToolbarBackground.getColor() == color) return;
mToolbarBackground.setColor(color);
setToolbarHairlineColor(color);
invalidate();
// We will set status bar's color same as the toolbar's color on phone form factor.
notifyToolbarColorChanged(color);
}
private void updateToolbarBackgroundFromState(@VisualState int visualState) {
updateToolbarBackground(getToolbarColorForVisualState(visualState));
}
private @ColorInt int getToolbarColorForVisualState(final @VisualState int visualState) {
switch (visualState) {
case VisualState.NEW_TAB_NORMAL:
// We are likely in the middle of a layout animation, and the NTP cannot draw itself
// yet. Use the NTP background color, which will match what the NTP eventually
// draws itself.
if (!getToolbarDataProvider().getNewTabPageDelegate().hasCompletedFirstLayout()) {
return mToolbarBackgroundColorForNtp;
}
// During transition we cannot rely on the background to be opaque yet, so keep full
// alpha until the transition is over.
int alpha = mInLayoutTransition ? 255 : Math.round(mUrlExpansionFraction * 255);
// When the NTP fake search box is visible, the background color should be
// transparent. When the location bar reaches the top of the screen (i.e. location
// bar is fully expanded), the background needs to change back to the NTP toolbar
// color so that the NTP content is not visible beneath the toolbar. In between the
// transition, we set a translucent NTP toolbar color based on the expansion
// progress of the toolbar.
return ColorUtils.setAlphaComponent(mToolbarBackgroundColorForNtp, alpha);
case VisualState.NEW_TAB_SEARCH_ENGINE_NO_LOGO:
return mToolbarBackgroundColorForNtp;
case VisualState.NORMAL:
if (mIsInLoadingPhaseFromNtpToWebpage) {
return mToolbarBackgroundColorForNtp;
}
return ChromeColors.getDefaultThemeColor(getContext(), false);
case VisualState.INCOGNITO:
return ChromeColors.getDefaultThemeColor(getContext(), true);
case VisualState.BRAND_COLOR:
if (mLocationBar.getPhoneCoordinator().hasFocus()) {
return getToolbarDefaultColor(/* shouldUseFocusColor= */ false);
}
return getToolbarDataProvider().getPrimaryColor();
default:
assert false;
return SemanticColorUtils.getToolbarBackgroundPrimary(getContext());
}
}
@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.getPhoneCoordinator().getVisibility() == VISIBLE
|| mTextureCaptureMode)) {
updateLocationBarBackgroundBounds(mLocationBarBackgroundBounds, mVisualState);
}
if (mTextureCaptureMode) {
drawWithoutBackground(canvas);
} else {
super.dispatchDraw(canvas);
}
}
@Override
protected boolean verifyDrawable(Drawable who) {
return super.verifyDrawable(who) || who == mActiveLocationBarBackground;
}
private void onNtpScrollChanged(float scrollFraction) {
mNtpSearchBoxScrollFraction = scrollFraction;
if (mNtpSearchBoxScrollFraction > 0) {
updateLocationBarForNtp(mVisualState, urlHasFocus());
}
updateUrlExpansionFraction();
updateUrlExpansionAnimation();
}
/**
* @return True if the toolbar is showing tab switcher assets, including during transitions.
*/
public boolean isInTabSwitcherMode() {
return mTabSwitcherState != STATIC_TAB;
}
/** Calculate the bounds for the location bar background and set them to {@code out}. */
private void updateLocationBarBackgroundBounds(Rect out, @VisualState int visualState) {
// Calculate the visible boundaries of the left and right most child views of the
// location bar.
int leftViewPosition = getLeftPositionOfLocationBarBackground(visualState);
int rightViewPosition = getRightPositionOfLocationBarBackground(visualState);
int verticalInset = mLocationBarBackgroundVerticalInset - calculateOnFocusHeightIncrease();
// 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.
out.set(
leftViewPosition,
mLocationBar.getPhoneCoordinator().getTop() + verticalInset,
rightViewPosition,
mLocationBar.getPhoneCoordinator().getBottom() - verticalInset);
}
/**
* @param visualState The current {@link VisualState} of the toolbar.
* @return The left drawing position for the location bar background.
*/
private int getLeftPositionOfLocationBarBackground(@VisualState int visualState) {
float expansion = getExpansionFractionForVisualState(visualState);
int leftViewPosition =
(int)
MathUtils.interpolate(
getViewBoundsLeftOfLocationBar(visualState),
getFocusedLeftPositionOfLocationBarBackground(),
expansion);
return leftViewPosition;
}
/**
* @return The left drawing position for the location bar background when the location bar
* has focus.
*/
private int getFocusedLeftPositionOfLocationBarBackground() {
return mToolbarSidePadding;
}
/**
* @param visualState The current {@link VisualState} of the toolbar.
* @return The right drawing position for the location bar background.
*/
private int getRightPositionOfLocationBarBackground(@VisualState int visualState) {
float expansion = getExpansionFractionForVisualState(visualState);
int rightViewPosition =
(int)
MathUtils.interpolate(
getViewBoundsRightOfLocationBar(visualState),
getFocusedRightPositionOfLocationBarBackground(),
expansion);
return rightViewPosition;
}
/**
* @return The difference in the location bar width when the optional button is hidden
* rather than showing. This is effectively the width of the optional button with
* some adjustment to account for possible padding differences when the button
* visibility changes.
*/
@VisibleForTesting
float getLocationBarWidthOffsetForOptionalButton() {
float widthChange = mOptionalButtonCoordinator.getViewWidth();
// When the optional button is the only visible button after the location bar and the
// button is hidden mToolbarSidePadding is used for the padding after the location bar.
if (!isMenuButtonPresent()) {
widthChange -= mToolbarSidePadding;
}
return widthChange;
}
/**
* @return The right drawing position for the location bar background when the location bar
* has focus.
*/
private int getFocusedRightPositionOfLocationBarBackground() {
return getWidth() - mToolbarSidePadding;
}
private float getExpansionFractionForVisualState(@VisualState int visualState) {
return visualState == VisualState.NEW_TAB_NORMAL && mTabSwitcherState == STATIC_TAB
? mUrlFocusChangeFraction
: mUrlExpansionFraction;
}
/**
* Updates progress of current the URL focus change animation.
*
* @param fraction 1.0 is 100% focused, 0 is completely unfocused.
*/
private void setUrlFocusChangeFraction(float fraction) {
mUrlFocusChangeFraction = fraction;
updateUrlExpansionFraction();
updateUrlExpansionAnimation();
}
private void updateUrlExpansionFraction() {
mUrlExpansionFraction = Math.max(mNtpSearchBoxScrollFraction, mUrlFocusChangeFraction);
for (UrlExpansionObserver observer : mUrlExpansionObservers) {
observer.onUrlExpansionProgressChanged();
}
assert mUrlExpansionFraction >= 0;
assert mUrlExpansionFraction <= 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() {
// TODO(crbug.com/40585866): Prevent url expansion signals from happening while the
// toolbar is not visible (e.g. in tab switcher mode).
if (isInTabSwitcherMode()) return;
int toolbarButtonVisibility = getToolbarButtonVisibility();
mToolbarButtonsContainer.setVisibility(toolbarButtonVisibility);
if (mHomeButton.getVisibility() != GONE) {
mHomeButton.setVisibility(toolbarButtonVisibility);
}
updateLocationBarLayoutForExpansionAnimation();
}
/**
* @return The visibility for {@link #mToolbarButtonsContainer}.
*/
private int getToolbarButtonVisibility() {
return (mUrlExpansionFraction == 1f) ? INVISIBLE : VISIBLE;
}
/**
* Updates the location bar layout, as the result of either a focus change or scrolling the New
* Tab Page.
*/
private void updateLocationBarLayoutForExpansionAnimation() {
TraceEvent.begin("ToolbarPhone.updateLocationBarLayoutForExpansionAnimation");
if (isInTabSwitcherMode()) return;
FrameLayout.LayoutParams locationBarLayoutParams =
mLocationBar.getPhoneCoordinator().getFrameLayoutParams();
int currentLeftMargin = locationBarLayoutParams.leftMargin;
int currentWidth = locationBarLayoutParams.width;
boolean isInNtp = mVisualState == VisualState.NEW_TAB_NORMAL;
float locationBarBaseTranslationX =
(mUrlFocusChangeInProgress && isInNtp
? getFocusedLeftPositionOfLocationBarBackground()
: mUnfocusedLocationBarLayoutLeft)
- currentLeftMargin;
if (mOptionalButtonAnimationRunning) {
// When showing the button, we disable location bar relayout
// (mDisableLocationBarRelayout), so the location bar's left margin and
// mUnfocusedLocationBarLayoutLeft have not been updated to take into account the
// appearance of the optional icon. The views to left of the location bar will
// be wider than mUnfocusedlocationBarLayoutLeft in RTL, so adjust the translation by
// that amount.
// When hiding the button, we force a relayout without the optional toolbar button
// (mLayoutLocationBarWithoutExtraButton). mUnfocusedLocationBarLayoutLeft reflects
// the view bounds left of the location bar, which still includes the optional
// button. The location bar left margin, however, has been adjusted to reflect its
// end value when the optional button is fully hidden. The
// locationBarBaseTranslationX above accounts for the difference between
// mUnfocusedLocationBarLayoutLeft and the location bar's current left margin.
locationBarBaseTranslationX +=
getViewBoundsLeftOfLocationBar(mVisualState) - mUnfocusedLocationBarLayoutLeft;
}
// When the dse icon is visible, the LocationBar needs additional translation to compensate
// for the dse icon being laid out when focused. This also affects the UrlBar, which is
// handled below. See comments in LocationBar#getLocationBarOffsetForFocusAnimation() for
// implementation details.
var profile = getToolbarDataProvider().getProfile();
if (profile == null
|| SearchEngineUtils.getForProfile(profile).shouldShowSearchEngineLogo()) {
locationBarBaseTranslationX += getLocationBarOffsetForFocusAnimation(hasFocus());
}
boolean isLocationBarRtl =
mLocationBar.getPhoneCoordinator().getLayoutDirection() == LAYOUT_DIRECTION_RTL;
if (isLocationBarRtl) {
locationBarBaseTranslationX += mUnfocusedLocationBarLayoutWidth - currentWidth;
}
locationBarBaseTranslationX *= 1f - mUrlExpansionFraction;
mLocationBarBackgroundNtpOffset.setEmpty();
mLocationBarNtpOffsetLeft = 0;
mLocationBarNtpOffsetRight = 0;
boolean isLocationBarShownInNtp = isLocationBarShownInNtp();
Tab currentTab = getToolbarDataProvider().getTab();
if (currentTab != null) {
getToolbarDataProvider()
.getNewTabPageDelegate()
.setUrlFocusChangeAnimationPercent(mUrlFocusChangeFraction);
if (isLocationBarShownInNtp) {
updateNtpTransitionAnimation();
} else {
// Reset these values in case we transitioned to a different page during the
// transition.
resetNtpAnimationValues();
}
}
float locationBarTranslationX;
if (isLocationBarRtl) {
locationBarTranslationX = locationBarBaseTranslationX + mLocationBarNtpOffsetRight;
} else {
locationBarTranslationX = locationBarBaseTranslationX + mLocationBarNtpOffsetLeft;
}
mLocationBar.getPhoneCoordinator().setTranslationX(locationBarTranslationX);
if (!mOptionalButtonAnimationRunning) {
boolean isUrlFocusChangeInProgressWithScrollCompleted =
mNtpSearchBoxScrollFraction == 1 && mUrlFocusChangeInProgress;
mUrlActionContainer.setTranslationX(
getUrlActionsTranslationXForExpansionAnimation(
isLocationBarRtl,
locationBarBaseTranslationX,
isUrlFocusChangeInProgressWithScrollCompleted));
mLocationBar.setUrlFocusChangeFraction(
mNtpSearchBoxScrollFraction, mUrlFocusChangeFraction);
// Only transition theme colors if in static tab mode that is not the NTP or while
// focusing on the NTP. In NTP, toolbar and locationbar need to transite color only when
// the omnibox is focused. When the fake omnibox is scrolled, the color should not
// change.
if ((mLocationBar.getPhoneCoordinator().hasFocus() || !isLocationBarShownInNtp)
&& mTabSwitcherState == STATIC_TAB) {
boolean isInGeneralNtp =
isLocationBarShownInGeneralNtp() || mIsInLoadingPhaseFromNtpToWebpage;
// Add a special case for general NTP to the defaultColor to ensure that the color
// is right and changes smoothly during the un-focus animation.
boolean shouldUseFocusColor = isInGeneralNtp && mUrlFocusChangeInProgress;
@ColorInt int defaultColor = getToolbarDefaultColor(shouldUseFocusColor);
@ColorInt
int defaultLocationBarColor =
getLocationBarDefaultColorForToolbarColor(
defaultColor, shouldUseFocusColor);
@ColorInt
int primaryColor =
isInGeneralNtp
? mToolbarBackgroundColorForNtp
: getToolbarDataProvider().getPrimaryColor();
@ColorInt
int themedLocationBarColor = getLocationBarColorForToolbarColor(primaryColor);
updateToolbarBackground(
ColorUtils.blendColorsMultiply(
primaryColor, defaultColor, mUrlFocusChangeFraction));
updateModernLocationBarColor(
ColorUtils.blendColorsMultiply(
themedLocationBarColor,
defaultLocationBarColor,
mUrlFocusChangeFraction));
updateModernLocationBarCorners();
}
}
// 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.getPhoneCoordinator().invalidate();
invalidate();
TraceEvent.end("ToolbarPhone.updateLocationBarLayoutForExpansionAnimation");
}
/**
* Calculates the translation X for the URL actions container for use in the URL expansion
* animation.
*
* @param isLocationBarRtl Whether the location bar layout is RTL.
* @param locationBarBaseTranslationX The base location bar translation for the URL expansion
* animation.
* @param isUrlFocusChangeInProgressWithScrollCompleted True if it is a focus or un-focus
* animation while the real omnibox is visible on NTP.
* @return The translation X for the URL actions container.
*/
private float getUrlActionsTranslationXForExpansionAnimation(
boolean isLocationBarRtl,
float locationBarBaseTranslationX,
boolean isUrlFocusChangeInProgressWithScrollCompleted) {
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
float urlActionsTranslationX = 0;
if (isUrlFocusChangeInProgressWithScrollCompleted) {
int urlActionContainerEndMarginChange =
getResources().getDimensionPixelSize(R.dimen.location_bar_url_action_offset_ntp)
- getResources()
.getDimensionPixelSize(R.dimen.location_bar_url_action_offset);
int toolbarSidePaddingChange = mToolbarSidePaddingForNtp - mToolbarSidePadding;
if (mLocationBar.getPhoneCoordinator().hasFocus()) {
urlActionsTranslationX =
MathUtils.flipSignIf(
(toolbarSidePaddingChange + urlActionContainerEndMarginChange)
* (mUrlFocusChangeFraction - 1),
isRtl);
} else {
urlActionsTranslationX =
MathUtils.flipSignIf(
toolbarSidePaddingChange * (mUrlFocusChangeFraction - 1)
+ urlActionContainerEndMarginChange
* mUrlFocusChangeFraction,
isRtl);
}
return urlActionsTranslationX;
}
if (!isLocationBarRtl || isRtl) {
// Negate the location bar translation to keep the URL action container in the same
// place during the focus expansion.
urlActionsTranslationX = -locationBarBaseTranslationX;
}
if (isRtl) {
urlActionsTranslationX +=
mLocationBarNtpOffsetLeft
- mLocationBarNtpOffsetRight
+ ((mVisualState == VisualState.NEW_TAB_NORMAL)
? mUrlActionsNtpEndOffset
: 0);
} else {
urlActionsTranslationX +=
mLocationBarNtpOffsetRight
- mLocationBarNtpOffsetLeft
- ((mVisualState == VisualState.NEW_TAB_NORMAL)
? mUrlActionsNtpEndOffset
: 0);
}
return urlActionsTranslationX;
}
/**
* 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();
mActiveLocationBarBackground = mLocationBarBackground;
mNtpSearchBoxTranslation.set(0, 0);
mLocationBar.getPhoneCoordinator().setTranslationY(0);
mLocationBar.getPhoneCoordinator().setTranslationX(0);
if (!mUrlFocusChangeInProgress) {
mToolbarButtonsContainer.setTranslationY(0);
mHomeButton.setTranslationY(0);
}
if (!mUrlFocusChangeInProgress && getToolbarShadow() != null) {
getToolbarShadow().setAlpha(mUrlBar.hasFocus() ? 0.f : 1.f);
}
mLocationBar.getPhoneCoordinator().setAlpha(1);
mForceDrawLocationBarBackground = false;
setAncestorsShouldClipChildren(true);
setClipToPadding(true);
mNtpSearchBoxScrollFraction = UNINITIALIZED_FRACTION;
updateUrlExpansionFraction();
}
/**
* 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;
boolean isExpanded = mUrlExpansionFraction > 0f;
setAncestorsShouldClipChildren(!isExpanded);
setClipToPadding(!isExpanded);
if (!mUrlFocusChangeInProgress) {
float alpha = 0.f;
if (!mUrlBar.hasFocus() && mNtpSearchBoxScrollFraction == 1.f) {
alpha = 1.f;
}
getToolbarShadow().setAlpha(alpha);
}
NewTabPageDelegate ntpDelegate = getToolbarDataProvider().getNewTabPageDelegate();
// #getSearchBoxBounds is only valid once the NTP can actually draw itself.
if (ntpDelegate.hasCompletedFirstLayout()) {
ntpDelegate.getSearchBoxBounds(mNtpSearchBoxBounds, mNtpSearchBoxTranslation);
int locationBarTranslationY =
Math.max(
0,
(mNtpSearchBoxBounds.top
- mLocationBar.getPhoneCoordinator().getTop()));
mLocationBar.getPhoneCoordinator().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 =
1f
- Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR.getInterpolation(
mUrlExpansionFraction);
int leftBoundDifference =
mUrlFocusChangeInProgress
? 0
: mNtpSearchBoxBounds.left - mLocationBarBackgroundBounds.left;
int rightBoundDifference =
mUrlFocusChangeInProgress
? 0
: mNtpSearchBoxBounds.right - mLocationBarBackgroundBounds.right;
mLocationBarBackgroundNtpOffset.set(
Math.round(leftBoundDifference * shrinkage),
locationBarTranslationY,
Math.round(rightBoundDifference * shrinkage),
locationBarTranslationY);
float urlExpansionFractionComplement = 1.f - mUrlExpansionFraction;
var resources = getResources();
int baseInset =
resources.getDimensionPixelSize(R.dimen.modern_toolbar_background_size)
- resources.getDimensionPixelSize(R.dimen.ntp_search_box_height);
int verticalInset = (int) (((float) (baseInset) / 2) * urlExpansionFractionComplement);
int locationBarUrlActionOffsetChange =
resources.getDimensionPixelSize(R.dimen.location_bar_url_action_offset_ntp)
- resources.getDimensionPixelSize(
R.dimen.location_bar_url_action_offset);
int focusChangeDelta = mUrlFocusChangeInProgress ? 0 : locationBarUrlActionOffsetChange;
mUrlActionsNtpEndOffset =
(resources.getDimensionPixelSize(R.dimen.fake_search_box_end_padding)
- focusChangeDelta)
* shrinkage;
mLocationBarBackgroundNtpOffset.inset(0, verticalInset);
if (mUrlFocusChangeInProgress) {
leftBoundDifference =
mNtpSearchBoxBounds.left - getFocusedLeftPositionOfLocationBarBackground();
rightBoundDifference =
mNtpSearchBoxBounds.right
- getFocusedRightPositionOfLocationBarBackground();
}
mLocationBarNtpOffsetLeft = leftBoundDifference * shrinkage;
mLocationBarNtpOffsetRight = rightBoundDifference * shrinkage;
}
mForceDrawLocationBarBackground = isExpanded;
float relativeAlpha = isExpanded ? 1f : 0f;
mLocationBar.getPhoneCoordinator().setAlpha(relativeAlpha);
// The search box on the NTP is visible if our omnibox is invisible, and vice-versa.
ntpDelegate.setSearchBoxAlpha(1f - relativeAlpha);
if (!mForceDrawLocationBarBackground) {
if (mActiveLocationBarBackground instanceof NtpSearchBoxDrawable) {
((NtpSearchBoxDrawable) mActiveLocationBarBackground).resetBoundsToLastNonToolbar();
}
}
updateToolbarBackgroundFromState(mVisualState);
}
/**
* 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;
ViewUtils.setAncestorsShouldClipChildren(this, clip);
}
/** Draws all the browsing mode views at full alpha, but without a background. */
@VisibleForTesting
void drawWithoutBackground(Canvas canvas) {
if (!isNativeLibraryReady()) return;
int rgbAlpha = 255;
canvas.save();
canvas.clipRect(mBackgroundOverlayBounds);
if (mHomeButton.getVisibility() != View.GONE) {
drawChild(canvas, mHomeButton, SystemClock.uptimeMillis());
}
// Draw the location/URL bar.
if (mLocationBar.getPhoneCoordinator().getAlpha() != 0 && isLocationBarCurrentlyShown()) {
drawLocationBar(canvas, SystemClock.uptimeMillis());
}
// Translate to draw end toolbar buttons.
ViewUtils.translateCanvasToView(this, mToolbarButtonsContainer, canvas);
// Draw the optional button if visible. We check for both visibility and width because in
// some cases (e.g. the first frame of the showing animation) the view may be visible with a
// width of zero. Calling draw in this state results in drawing the inner ImageButton when
// it's not supposed to. (See https://crbug.com/1422176 for an example of this happening).
if (mOptionalButtonCoordinator != null
&& mOptionalButtonCoordinator.getViewVisibility() != View.GONE
&& mOptionalButtonCoordinator.getViewWidth() != 0) {
canvas.save();
ViewUtils.translateCanvasToView(
mToolbarButtonsContainer,
mOptionalButtonCoordinator.getViewForDrawing(),
canvas);
mOptionalButtonCoordinator.getViewForDrawing().draw(canvas);
canvas.restore();
}
// Draw the tab stack button and associated text if necessary.
if (getTabSwitcherButtonCoordinator() != null && mUrlExpansionFraction != 1f) {
// Draw the tab stack button image.
getTabSwitcherButtonCoordinator()
.drawTabSwitcherAnimationOverlay(mToolbarButtonsContainer, canvas, rgbAlpha);
}
// Draw the menu button if necessary.
final MenuButtonCoordinator menuButtonCoordinator = getMenuButtonCoordinator();
if (menuButtonCoordinator != null) {
menuButtonCoordinator.drawTabSwitcherAnimationOverlay(
mToolbarButtonsContainer, canvas, rgbAlpha);
}
canvas.restore();
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
if (mLocationBar != null
&& child == mLocationBar.getPhoneCoordinator().getViewForDrawing()) {
return drawLocationBar(canvas, drawingTime);
}
boolean clipped = false;
if (mLocationBarBackground != null) {
canvas.save();
int translationY = (int) mLocationBar.getPhoneCoordinator().getTranslationY();
int clipTop = mLocationBarBackgroundBounds.top + translationY;
if (mUrlExpansionFraction != 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 = isChildLeft(child);
int clipBottom = mLocationBarBackgroundBounds.bottom + translationY;
boolean verticalClip = false;
if (translationY > 0f) {
clipTop = child.getTop();
clipBottom = clipTop;
verticalClip = true;
}
if (isLeft) {
int clipRight =
verticalClip
? child.getMeasuredWidth()
: mLocationBarBackgroundBounds.left;
canvas.clipRect(0, clipTop, clipRight, clipBottom);
} else {
int clipLeft = verticalClip ? 0 : mLocationBarBackgroundBounds.right;
canvas.clipRect(clipLeft, clipTop, getMeasuredWidth(), clipBottom);
}
}
clipped = true;
}
boolean retVal = super.drawChild(canvas, child, drawingTime);
if (clipped) canvas.restore();
return retVal;
}
private boolean isChildLeft(View child) {
return child == mHomeButton ^ LocalizationUtils.isLayoutRtl();
}
/**
* @return Whether or not the location bar should be drawing at any particular state of the
* toolbar.
*/
private boolean shouldDrawLocationBar() {
// The location bar should have alpha or clip+translation when its not supposed to be
// shown. Needs to be drawn during transitions, such as entering/exiting tab switcher.
return mLocationBarBackground != null;
}
private boolean drawLocationBar(Canvas canvas, long drawingTime) {
TraceEvent.begin("ToolbarPhone.drawLocationBar");
boolean clipped = false;
if (shouldDrawLocationBar()) {
canvas.save();
if (shouldDrawLocationBarBackground()) {
if (mActiveLocationBarBackground instanceof NtpSearchBoxDrawable) {
((NtpSearchBoxDrawable) mActiveLocationBarBackground)
.markPendingBoundsUpdateFromToolbar();
}
mActiveLocationBarBackground.setBounds(
mLocationBarBackgroundBounds.left + mLocationBarBackgroundNtpOffset.left,
mLocationBarBackgroundBounds.top + mLocationBarBackgroundNtpOffset.top,
mLocationBarBackgroundBounds.right + mLocationBarBackgroundNtpOffset.right,
mLocationBarBackgroundBounds.bottom
+ mLocationBarBackgroundNtpOffset.bottom);
mActiveLocationBarBackground.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;
final int locationBarPaddingStart =
mLocationBar.getPhoneCoordinator().getPaddingStart();
final int locationBarPaddingEnd = mLocationBar.getPhoneCoordinator().getPaddingEnd();
final int locationBarDirection =
mLocationBar.getPhoneCoordinator().getLayoutDirection();
// 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 the visible content is the same.
if (mUrlExpansionFraction != 1f && !mOptionalButtonAnimationRunning) {
int leftDelta =
mUnfocusedLocationBarLayoutLeft
- getViewBoundsLeftOfLocationBar(mVisualState);
int rightDelta =
getViewBoundsRightOfLocationBar(mVisualState)
- mUnfocusedLocationBarLayoutLeft
- mUnfocusedLocationBarLayoutWidth;
float remainingFraction = 1f - mUrlExpansionFraction;
locationBarClipLeft += leftDelta * remainingFraction;
locationBarClipRight -= rightDelta * remainingFraction;
// When the defocus animation is running, the location bar padding needs to be
// subtracted from the clip bounds so that the location bar text width in the last
// frame of the animation matches the text width of the unfocused location bar.
if (locationBarDirection == LAYOUT_DIRECTION_RTL) {
locationBarClipLeft += locationBarPaddingStart * remainingFraction;
} else {
locationBarClipRight -= locationBarPaddingEnd * remainingFraction;
}
}
if (mOptionalButtonAnimationRunning) {
if (locationBarDirection == LAYOUT_DIRECTION_RTL) {
locationBarClipLeft += locationBarPaddingStart;
} else {
locationBarClipRight -= locationBarPaddingEnd;
}
}
// Offset the clip rect by a set amount to ensure the Google G is completely inside the
// omnibox background when animating in.
var profile = getToolbarDataProvider().getProfile();
if ((profile == null
|| SearchEngineUtils.getForProfile(profile)
.shouldShowSearchEngineLogo())
&& isLocationBarShownInNtp()
&& urlHasFocus()
&& mUrlFocusChangeInProgress) {
if (locationBarDirection == LAYOUT_DIRECTION_RTL) {
locationBarClipRight -= locationBarPaddingStart;
} else {
locationBarClipLeft += locationBarPaddingStart;
}
}
// Clip the location bar child to the URL viewport calculated in onDraw.
canvas.clipRect(
locationBarClipLeft,
locationBarClipTop,
locationBarClipRight,
locationBarClipBottom);
clipped = true;
}
// TODO(crbug.com/40151029): Hide this View interaction if possible.
boolean retVal =
super.drawChild(
canvas,
mLocationBar.getPhoneCoordinator().getViewForDrawing(),
drawingTime);
if (clipped) canvas.restore();
TraceEvent.end("ToolbarPhone.drawLocationBar");
return retVal;
}
/**
* @return Whether the location bar background should be drawn in {@link
* #drawLocationBar(Canvas, long)}.
*/
private boolean shouldDrawLocationBarBackground() {
return (mLocationBar.getPhoneCoordinator().getAlpha() > 0
|| mForceDrawLocationBarBackground)
&& !mTextureCaptureMode;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mBackgroundOverlayBounds.set(0, 0, w, h);
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public void draw(Canvas canvas) {
if (mDestroyChecker.isDestroyed()) return;
// 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.
assert !mTextureCaptureMode || getAlpha() == 1f;
super.draw(canvas);
}
@Override
public CaptureReadinessResult isReadyForTextureCapture() {
if (mForceTextureCapture) {
return CaptureReadinessResult.readyForced();
} else if (ToolbarFeatures.shouldSuppressCaptures()) {
return getReadinessStateWithSuppression();
} else {
return CaptureReadinessResult.unknown(!(urlHasFocus() || mUrlFocusChangeInProgress));
}
}
private CaptureReadinessResult getReadinessStateWithSuppression() {
if (urlHasFocus()) {
return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.URL_BAR_HAS_FOCUS);
} else if (mUrlFocusChangeInProgress) {
return CaptureReadinessResult.notReady(
TopToolbarBlockCaptureReason.URL_BAR_FOCUS_IN_PROGRESS);
} else if (mOptionalButtonAnimationRunning) {
return CaptureReadinessResult.notReady(
TopToolbarBlockCaptureReason.OPTIONAL_BUTTON_ANIMATION_IN_PROGRESS);
} else if (mLocationBar.getStatusCoordinator() != null
&& mLocationBar.getStatusCoordinator().isStatusIconAnimating()) {
// TODO(crbug.com/40860241): It may be possible to remove the above null check.
return CaptureReadinessResult.notReady(
TopToolbarBlockCaptureReason.STATUS_ICON_ANIMATION_IN_PROGRESS);
} else if (isInTabSwitcherMode()) {
return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.TAB_SWITCHER_MODE);
} else if (mNtpSearchBoxTranslation.y != 0) {
return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.NTP_Y_TRANSLATION);
} else {
PhoneCaptureStateToken newSnapshotState = generateToolbarSnapshotState();
@ToolbarSnapshotDifference
int snapshotDifference =
PhoneCaptureStateToken.getAnyDifference(
mPhoneCaptureStateToken, newSnapshotState);
if (snapshotDifference == ToolbarSnapshotDifference.NONE) {
return CaptureReadinessResult.notReady(TopToolbarBlockCaptureReason.SNAPSHOT_SAME);
} else {
return CaptureReadinessResult.readyWithSnapshotDifference(snapshotDifference);
}
}
}
@Override
public boolean setForceTextureCapture(boolean forceTextureCapture) {
if (forceTextureCapture) {
// Only force a texture capture if the tint for the toolbar drawables is changing or
// if the tab count has changed since the last texture capture.
if (mPhoneCaptureStateToken == null) {
mPhoneCaptureStateToken = generateToolbarSnapshotState();
}
mForceTextureCapture = mPhoneCaptureStateToken.getTint() != getTint().getDefaultColor();
if (getTabSwitcherButtonCoordinator() != null) {
mForceTextureCapture =
mForceTextureCapture
|| mPhoneCaptureStateToken.getTabCount()
!= getTabSwitcherButtonCoordinator().getDrawableTabCount();
}
return mForceTextureCapture;
}
mForceTextureCapture = false;
return false;
}
private PhoneCaptureStateToken generateToolbarSnapshotState() {
UrlBarData urlBarData;
@DrawableRes int securityIconResource;
if (ToolbarFeatures.shouldSuppressCaptures()) {
urlBarData = mLocationBar.getUrlBarData();
if (urlBarData == null) urlBarData = getToolbarDataProvider().getUrlBarData();
StatusCoordinator statusCoordinator = mLocationBar.getStatusCoordinator();
securityIconResource =
statusCoordinator == null
? getToolbarDataProvider().getSecurityIconResource(false)
: statusCoordinator.getSecurityIconResource();
} else {
urlBarData = getToolbarDataProvider().getUrlBarData();
securityIconResource = getToolbarDataProvider().getSecurityIconResource(false);
}
VisibleUrlText visibleUrlText =
new VisibleUrlText(
urlBarData.displayText, mLocationBar.getOmniboxVisibleTextPrefixHint());
return new PhoneCaptureStateToken(
getTint().getDefaultColor(),
mTabCountSupplier == null ? 0 : mTabCountSupplier.get(),
mButtonData,
mVisualState,
visibleUrlText,
securityIconResource,
ImageViewCompat.getImageTintList(mHomeButton),
mHomeButton.getVisibility() == View.VISIBLE,
getMenuButtonCoordinator().isShowingUpdateBadge(),
getToolbarDataProvider().isPaintPreview(),
getProgressBar().getProgress(),
mUnfocusedLocationBarLayoutWidth);
}
@Override
public void setLayoutUpdater(Runnable layoutUpdater) {
mLayoutUpdater = layoutUpdater;
}
@Override
public void finishAnimations() {
// 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
public void onHomeButtonUpdate(boolean homeButtonEnabled) {
mIsHomeButtonEnabled = homeButtonEnabled;
updateButtonVisibility();
}
@Override
public void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
updateButtonVisibility();
}
@Override
public void updateButtonVisibility() {
boolean hideHomeButton = !mIsHomeButtonEnabled;
if (hideHomeButton) {
mHomeButton.setVisibility(View.GONE);
} else {
mHomeButton.setVisibility(urlHasFocus() ? INVISIBLE : VISIBLE);
}
}
@Override
public void onTintChanged(
ColorStateList tint,
ColorStateList activityFocusTint,
@BrandedColorScheme int brandedColorScheme) {
ImageViewCompat.setImageTintList(mHomeButton, tint);
if (getTabSwitcherButtonCoordinator() != null) {
getTabSwitcherButtonCoordinator().setBrandedColorScheme(brandedColorScheme);
}
if (mOptionalButtonCoordinator != null) {
mOptionalButtonCoordinator.setIconForegroundColor(tint);
}
// TODO(amaralp): Have the LocationBar listen to tint changes.
if (mLocationBar != null) mLocationBar.updateVisualsForState();
if (mLayoutUpdater != null) mLayoutUpdater.run();
}
@Override
public ImageView getHomeButton() {
return mHomeButton;
}
@Override
public void setTextureCaptureMode(boolean textureMode) {
assert mTextureCaptureMode != textureMode;
mTextureCaptureMode = textureMode;
if (mTextureCaptureMode) {
if (!hideShadowForIncognitoNtp()
&& !hideShadowForInterstitial()
&& !hideShadowForRegularNtpTextureCapture()) {
getToolbarShadow().setVisibility(VISIBLE);
}
mPreTextureCaptureAlpha = getAlpha();
mPreTextureCaptureVisibility = getVisibility();
setAlpha(1);
setVisibility(View.VISIBLE);
} else {
setAlpha(mPreTextureCaptureAlpha);
setVisibility(mPreTextureCaptureVisibility);
updateShadowVisibility();
mPreTextureCaptureAlpha = 1f;
// When texture mode is turned off, we know a capture has just been completed. Update
// our snapshot so that we can suppress correctly on the next
// #isReadyForTextureCapture() call.
mPhoneCaptureStateToken = generateToolbarSnapshotState();
}
}
private boolean hideShadowForRegularNtpTextureCapture() {
return !isIncognitoBranded()
&& UrlUtilities.isNtpUrl(getToolbarDataProvider().getCurrentGurl())
&& mNtpSearchBoxScrollFraction < 1.f;
}
private void updateViewsForTabSwitcherMode() {
setVisibility(mTabSwitcherState == TAB_SWITCHER ? View.INVISIBLE : View.VISIBLE);
updateProgressBarVisibility();
updateShadowVisibility();
}
private void updateProgressBarVisibility() {
getProgressBar().setVisibility(mTabSwitcherState != STATIC_TAB ? INVISIBLE : VISIBLE);
}
@Override
public void setContentAttached(boolean attached) {
updateVisualsForLocationBarState();
}
@Override
public void setTabSwitcherMode(boolean inTabSwitcherMode) {
// On entering the tab switcher, set the focusability of the url bar to be false. This will
// occur at the start of the enter event, and will later be reset to true upon finishing the
// exit event.
if (inTabSwitcherMode) {
mLocationBar.setUrlBarFocusable(false);
}
// 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;
// The width of location bar depends on mTabSwitcherState so layout request is needed. See
// crbug.com/974745.
ViewUtils.requestLayout(this, "ToolbarPhone.setTabSwitcherMode");
finishAnimations();
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());
}
updateViewsForTabSwitcherMode();
}
// Since mTabSwitcherState has changed, we need to also check if mVisualState should
// change.
updateVisualsForLocationBarState();
updateButtonsTranslationY();
postInvalidateOnAnimation();
}
@Override
public void onTabSwitcherTransitionFinished() {
setAlpha(1.f);
// Detect what was being transitioned from and set the new state appropriately.
if (mTabSwitcherState == EXITING_TAB_SWITCHER) {
mLocationBar.setUrlBarFocusable(true);
mTabSwitcherState = STATIC_TAB;
updateVisualsForLocationBarState();
}
if (mTabSwitcherState == ENTERING_TAB_SWITCHER) {
mTabSwitcherState = TAB_SWITCHER;
}
// The width of location bar depends on mTabSwitcherState so layout request is needed. See
// crbug.com/974745.
ViewUtils.requestLayout(this, "ToolbarPhone.onTabSwitcherTransitionFinished");
finishAnimations();
updateVisualsForLocationBarState();
updateViewsForTabSwitcherMode();
}
@Override
public boolean shouldIgnoreSwipeGesture() {
return super.shouldIgnoreSwipeGesture()
|| mUrlExpansionFraction > 0f
|| mNtpSearchBoxTranslation.y < 0f;
}
private void populateUrlExpansionAnimatorSet(List<Animator> animators) {
TraceEvent.begin("ToolbarPhone.populateUrlFocusingAnimatorSet");
Animator animator = ObjectAnimator.ofFloat(this, mUrlFocusChangeFractionProperty, 1f);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
animators.add(animator);
mLocationBar
.getPhoneCoordinator()
.populateFadeAnimation(animators, 0, URL_FOCUS_CHANGE_ANIMATION_DURATION_MS, 0);
float density = getContext().getResources().getDisplayMetrics().density;
boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
float toolbarButtonTranslationX =
MathUtils.flipSignIf(mUrlFocusTranslationX, isRtl) * density;
// Hide the toolbar buttons immediately on the NTP when animating in the suggestions list to
// avoid mixing horizontal and vertical motion; the suggestions translate in vertically.
int toolbarButtonFadeDuration =
animatingSuggestionsListOnNtp() ? 0 : URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS;
animator = getMenuButtonCoordinator().getUrlFocusingAnimator(true);
animator.setDuration(toolbarButtonFadeDuration);
animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animators.add(animator);
animator =
ObjectAnimator.ofFloat(
mHomeButton,
TRANSLATION_X,
MathUtils.flipSignIf(-mHomeButton.getWidth() * density, isRtl));
animator.setDuration(toolbarButtonFadeDuration);
animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animators.add(animator);
if (getTabSwitcherButtonCoordinator() != null) {
animator =
ObjectAnimator.ofFloat(
getTabSwitcherButtonCoordinator().getContainerView(),
TRANSLATION_X,
toolbarButtonTranslationX);
animator.setDuration(toolbarButtonFadeDuration);
animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animators.add(animator);
animator =
ObjectAnimator.ofFloat(
getTabSwitcherButtonCoordinator().getContainerView(), ALPHA, 0);
animator.setDuration(toolbarButtonFadeDuration);
animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animators.add(animator);
}
if (getToolbarShadow() != null) {
animator = ObjectAnimator.ofFloat(getToolbarShadow(), ALPHA, urlHasFocus() ? 0 : 1);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
animators.add(animator);
}
TraceEvent.end("ToolbarPhone.populateUrlFocusingAnimatorSet");
}
private void populateUrlClearExpansionAnimatorSet(List<Animator> animators) {
Animator animator = ObjectAnimator.ofFloat(this, mUrlFocusChangeFractionProperty, 0f);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
animators.add(animator);
animator = getMenuButtonCoordinator().getUrlFocusingAnimator(false);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animators.add(animator);
animator = ObjectAnimator.ofFloat(mHomeButton, TRANSLATION_X, 0);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN_INTERPOLATOR);
animators.add(animator);
if (getTabSwitcherButtonCoordinator() != null) {
animator =
ObjectAnimator.ofFloat(
getTabSwitcherButtonCoordinator().getContainerView(), TRANSLATION_X, 0);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setStartDelay(URL_CLEAR_FOCUS_TABSTACK_DELAY_MS);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
animators.add(animator);
animator =
ObjectAnimator.ofFloat(
getTabSwitcherButtonCoordinator().getContainerView(), ALPHA, 1);
animator.setDuration(URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS);
animator.setStartDelay(URL_CLEAR_FOCUS_TABSTACK_DELAY_MS);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
animators.add(animator);
}
mLocationBar
.getPhoneCoordinator()
.populateFadeAnimation(
animators,
URL_FOCUS_TOOLBAR_BUTTONS_DURATION_MS,
URL_CLEAR_FOCUS_MENU_DELAY_MS,
1);
if (isLocationBarShownInNtp() && mNtpSearchBoxScrollFraction == 0f) return;
if (getToolbarShadow() != null) {
animator = ObjectAnimator.ofFloat(getToolbarShadow(), ALPHA, 1);
animator.setDuration(URL_FOCUS_CHANGE_ANIMATION_DURATION_MS);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
animators.add(animator);
}
}
@Override
public void onUrlFocusChange(final boolean hasFocus) {
super.onUrlFocusChange(hasFocus);
updateBackground(hasFocus);
updateLocationBarForNtp(mVisualState, urlHasFocus());
getTabSwitcherButtonCoordinator().getContainerView().setClickable(!hasFocus);
triggerUrlFocusAnimation(hasFocus);
}
private boolean animatingSuggestionsListOnNtp() {
return OmniboxFeatures.shouldAnimateSuggestionsListAppearance()
&& getToolbarDataProvider().getNewTabPageDelegate().isLocationBarShown();
}
/**
* @param hasFocus Whether the URL field has gained focus.
*/
private void updateBackground(final boolean hasFocus) {
if (hasFocus) {
mDropdownListScrolled = false;
mActiveLocationBarBackground = mLocationBarBackground;
} else if (isLocationBarShownInNtp()) {
updateToNtpBackground();
}
}
private void updateToNtpBackground() {
// TODO(crbug.com/41410296): Make the ripple drawable move properly from fake omnibox
// to real omnibox when Build.VERSION.SDK_INT < Build.VERSION_CODES.P.
NtpSearchBoxDrawable ntpSearchBoxBackground = new NtpSearchBoxDrawable(getContext(), this);
getToolbarDataProvider()
.getNewTabPageDelegate()
.setSearchBoxBackground(ntpSearchBoxBackground);
mActiveLocationBarBackground = ntpSearchBoxBackground;
}
/**
* @param hasFocus Whether the URL field has gained focus.
*/
private void triggerUrlFocusAnimation(final boolean hasFocus) {
boolean shouldShowKeyboard = urlHasFocus();
TraceEvent.begin("ToolbarPhone.triggerUrlFocusAnimation");
if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) {
mUrlFocusLayoutAnimator.cancel();
mUrlFocusLayoutAnimator = null;
}
if (mOptionalButtonAnimationRunning) mOptionalButtonCoordinator.cancelTransition();
if (hasFocus && mBrandColorTransitionActive) {
mBrandColorTransitionAnimation.cancel();
}
List<Animator> animators = new ArrayList<>();
if (hasFocus) {
populateUrlExpansionAnimatorSet(animators);
} else {
populateUrlClearExpansionAnimatorSet(animators);
}
mUrlFocusLayoutAnimator = new AnimatorSet();
mUrlFocusLayoutAnimator.playTogether(animators);
mUrlFocusChangeInProgress = true;
// Hide the optional button immediately when animating in the suggestions list (since other
// toolbar buttons are also hidden immediately) or restore it when omnibox focus is lost.
if (animatingSuggestionsListOnNtp()) {
ButtonData copy = mButtonData;
updateOptionalButton(hasFocus ? null : mButtonData);
mButtonData = copy;
}
mUrlFocusLayoutAnimator.addListener(
new CancelAwareAnimatorListener() {
@Override
public void onStart(Animator animation) {
if (!hasFocus) {
mDisableLocationBarRelayout = true;
} else {
mLayoutLocationBarInFocusedMode = true;
ViewUtils.requestLayout(
ToolbarPhone.this,
"ToolbarPhone.triggerUrlFocusAnimation.CancelAwareAnimatorListener.onStart");
}
}
@Override
public void onCancel(Animator animation) {
if (!hasFocus) mDisableLocationBarRelayout = false;
mUrlFocusChangeInProgress = false;
}
@Override
public void onEnd(Animator animation) {
if (!hasFocus) {
mDisableLocationBarRelayout = false;
mLayoutLocationBarInFocusedMode = false;
ViewUtils.requestLayout(
ToolbarPhone.this,
"ToolbarPhone.triggerUrlFocusAnimation.CancelAwareAnimatorListener.onEnd");
}
mLocationBar.finishUrlFocusChange(hasFocus, shouldShowKeyboard);
mUrlFocusChangeInProgress = false;
}
});
mUrlFocusLayoutAnimator.start();
if (!hasFocus) {
TraceEvent.instant("ToolbarPhone.ShortCircuitUnfocusAnimation");
mUrlFocusLayoutAnimator.end();
}
TraceEvent.end("ToolbarPhone.triggerUrlFocusAnimation");
}
@Override
public void setTabCountSupplier(ObservableSupplier<Integer> tabCountSupplier) {
mTabCountSupplier = tabCountSupplier;
mTabCountSupplier.addObserver(mTabCountSupplierObserver);
}
private void onTabCountChanged(int numberOfTabs) {
mHomeButton.setEnabled(true);
if (getTabSwitcherButtonCoordinator() != null) {
// In some use cases, isIncognitoBranded() will return a stale value for whether the
// current view is in incognito. The mismatched value will result in the retrieval of
// an incorrect branded color scheme (such as incognito when it is non-incognito). This
// leads to problems downstream when setting the tint for the TabSwitcherDrawable. Using
// the supplier incognito value from the button coordinator will get the correct value.
Supplier<Boolean> isIncognitoSupplier =
getTabSwitcherButtonCoordinator().getIsIncognitoSupplier();
boolean isIncognito = isIncognitoBranded();
if (isIncognitoSupplier != null) {
isIncognito = isIncognitoSupplier.get();
}
@BrandedColorScheme
int overlayTabStackDrawableScheme =
OmniboxResourceProvider.getBrandedColorScheme(
getContext(), isIncognito, getTabThemeColor());
getTabSwitcherButtonCoordinator().setBrandedColorScheme(overlayTabStackDrawableScheme);
}
}
/**
* Get the theme color for the currently active tab. This is not affected by the tab switcher's
* theme color.
*
* @return The current tab's theme color.
*/
private @ColorInt int getTabThemeColor() {
if (getToolbarDataProvider() != null) return getToolbarDataProvider().getPrimaryColor();
return getToolbarColorForVisualState(
isIncognitoBranded() ? VisualState.INCOGNITO : VisualState.NORMAL);
}
@Override
public void onTabContentViewChanged() {
super.onTabContentViewChanged();
updateNtpAnimationState();
updateVisualsForLocationBarState();
}
@Override
public void onDidFirstVisuallyNonEmptyPaint() {
super.onDidFirstVisuallyNonEmptyPaint();
if (mIsInLoadingPhaseFromNtpToWebpage) {
// Updates toolbar and location bar's color to the default color when transitioning from
// NTP to other webpages before {@link ToolbarPhone#onPrimaryColorChanged(boolean)}
// is triggered. TODO(crbug.com/323888159): This code was created to deal with the
// case when the navigating webpage does not have the "theme-color" meta element or does
// not have its own brand color. In this situation, the function and {@link
// ToolbarPhone#onPrimaryColorChanged(boolean)} won't be triggered. This solution has
// limitations when dealing with webpages that use a brand color, as there may be a few
// frames with incorrect colors before the correct brand color is loaded. This code will
// be removed once a better solution is developed.
onPrimaryColorChanged(/* shouldAnimate= */ true);
}
}
@Override
public void onTabOrModelChanged() {
super.onTabOrModelChanged();
updateNtpAnimationState();
updateVisualsForLocationBarState();
}
private static boolean isVisualStateValidForBrandColorTransition(@VisualState int state) {
return state == VisualState.NORMAL
|| state == VisualState.BRAND_COLOR
|| state == VisualState.NEW_TAB_SEARCH_ENGINE_NO_LOGO;
}
@Override
public void onPrimaryColorChanged(boolean shouldAnimate) {
super.onPrimaryColorChanged(shouldAnimate);
if (mBrandColorTransitionActive) mBrandColorTransitionAnimation.end();
final @ColorInt int initialColor = mToolbarBackground.getColor();
final @ColorInt int finalColor =
isLocationBarShownInGeneralNtp()
? mToolbarBackgroundColorForNtp
: getToolbarDataProvider().getPrimaryColor();
if (initialColor == finalColor) return;
final @ColorInt int initialLocationBarColor =
getLocationBarColorForToolbarColor(initialColor);
// When the webpage finishes loading during the NTP phase, the process should halt at this
// point because the tab's color is updated, and the initial color of the location bar is
// established for the upcoming navigation animation.
mIsInLoadingPhaseFromNtpToWebpage = false;
final @ColorInt int finalLocationBarColor = getLocationBarColorForToolbarColor(finalColor);
// Ignore theme color changes while the omnibox is focused, since we want a standard,
// app-defined color for the toolbar in this scenario, not the site's color. We'll
// transition back to the site's color on unfocus.
if (urlHasFocus() || !isVisualStateValidForBrandColorTransition(mVisualState)) return;
if (!shouldAnimate) {
updateToolbarBackground(finalColor);
return;
}
mBrandColorTransitionAnimation =
ValueAnimator.ofFloat(0, 1).setDuration(THEME_COLOR_TRANSITION_DURATION);
mBrandColorTransitionAnimation.setInterpolator(Interpolators.FAST_OUT_SLOW_IN_INTERPOLATOR);
mBrandColorTransitionAnimation.addUpdateListener(
(ValueAnimator animation) -> {
float fraction = animation.getAnimatedFraction();
updateToolbarBackground(
ColorUtils.blendColorsMultiply(initialColor, finalColor, fraction));
updateModernLocationBarColor(
ColorUtils.blendColorsMultiply(
initialLocationBarColor, finalLocationBarColor, fraction));
});
mBrandColorTransitionAnimation.addListener(
new CancelAwareAnimatorListener() {
@Override
public void onEnd(Animator animation) {
mBrandColorTransitionActive = false;
updateVisualsForLocationBarState();
}
});
mBrandColorTransitionAnimation.start();
mBrandColorTransitionActive = true;
if (mLayoutUpdater != null) mLayoutUpdater.run();
}
private void updateNtpAnimationState() {
NewTabPageDelegate ntpDelegate = getToolbarDataProvider().getNewTabPageDelegate();
// Store previous NTP scroll before calling reset as that clears this value.
boolean wasShowingNtp = ntpDelegate.wasShowingNtp();
float previousNtpScrollFraction = mNtpSearchBoxScrollFraction;
resetNtpAnimationValues();
ntpDelegate.setSearchBoxScrollListener(this::onNtpScrollChanged);
if (ntpDelegate.isLocationBarShown()) {
updateToNtpBackground();
ViewUtils.requestLayout(
this, "ToolbarPhone.updateNtpAnimationState showing LocationBar");
} else if (wasShowingNtp) {
// Convert the previous NTP scroll progress to URL focus progress 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 && previousNtpScrollFraction > 0f) {
mUrlFocusChangeFraction =
Math.max(previousNtpScrollFraction, mUrlFocusChangeFraction);
triggerUrlFocusAnimation(false);
}
ViewUtils.requestLayout(this, "ToolbarPhone.updateNtpAnimationState showing ntp");
}
}
@Override
public 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.
Handler handler = getHandler();
if (handler == null) return;
handler.post(
() -> {
updateVisualsForLocationBarState();
updateNtpAnimationState();
});
}
@Override
public void handleFindLocationBarStateChange(boolean showing) {
setVisibility(
showing
? View.GONE
: mTabSwitcherState == STATIC_TAB ? View.VISIBLE : View.INVISIBLE);
}
@VisibleForTesting
boolean isLocationBarShownInNtp() {
return getToolbarDataProvider().getNewTabPageDelegate().isLocationBarShown();
}
boolean isLocationBarShownInGeneralNtp() {
return !isIncognito() && UrlUtilities.isNtpUrl(getToolbarDataProvider().getCurrentGurl());
}
private boolean isLocationBarCurrentlyShown() {
return !isLocationBarShownInNtp() || mUrlExpansionFraction > 0;
}
/**
* @return Whether the toolbar shadow should be drawn.
*/
@Override
protected boolean shouldDrawShadow() {
// TODO(twellington): Move this shadow state information to ToolbarDataProvider and show
// shadow when incognito NTP is scrolled.
return super.shouldDrawShadow()
&& mTabSwitcherState == STATIC_TAB
&& !hideShadowForIncognitoNtp()
&& !hideShadowForInterstitial()
&& getVisibility() == View.VISIBLE;
}
private boolean hideShadowForIncognitoNtp() {
return isIncognitoBranded()
&& UrlUtilities.isNtpUrl(getToolbarDataProvider().getCurrentGurl());
}
private boolean hideShadowForInterstitial() {
return getToolbarDataProvider() != null
&& getToolbarDataProvider().getTab() != null
&& (getToolbarDataProvider().getTab().isShowingErrorPage());
}
private @VisualState int computeVisualState() {
if (isLocationBarShownInNtp()) return VisualState.NEW_TAB_NORMAL;
if (isLocationBarShownInGeneralNtp()) {
return VisualState.NEW_TAB_SEARCH_ENGINE_NO_LOGO;
}
if (isIncognitoBranded()) return VisualState.INCOGNITO;
if (getToolbarDataProvider().isUsingBrandColor()) return VisualState.BRAND_COLOR;
return VisualState.NORMAL;
}
/**
* @return The color that progress bar should use.
*/
private @ColorInt int getProgressBarColor() {
return getToolbarDataProvider().getPrimaryColor();
}
/**
* Update the appearance (logo background, search text's color and style) of the location bar
* based on the state of the current page. For NTP, while not on the focus state, the real
* search box's search text has a particular color and style. The style would be
* "google-sans-medium" and the color would be colorOnSurface. When being in light mode, there
* is also a round white background for the G logo. For other situations such as browser tabs,
* and focus state, the real search box will stay the same.
*
* @param visualState The Visual State of the current page.
* @param hasFocus True if the current page is the focus state.
*/
@VisibleForTesting
void updateLocationBarForNtp(@VisualState int visualState, boolean hasFocus) {
// Detect whether state has changed and update only when that happens.
boolean prevIsNtpWithUnfocusedRealOmnibox = mIsNtpWithUnfocusedRealOmnibox;
// Check whether the current page is NTP (the focus state is not included) and the
// real omnibox is pinned on the top of the screen. We need to make sure the real omnibox is
// visible here to forbid the situation that the background of the G logo shows and then
// vanishes during the un-focus animation(from focus state to NTP).
boolean isNtpShowingWithRealOmnibox =
visualState == VisualState.NEW_TAB_NORMAL && mNtpSearchBoxScrollFraction > 0;
mIsNtpWithUnfocusedRealOmnibox = isNtpShowingWithRealOmnibox && !hasFocus;
if (mIsNtpWithUnfocusedRealOmnibox == prevIsNtpWithUnfocusedRealOmnibox) {
return;
}
if (mIsNtpWithUnfocusedRealOmnibox) {
updateLocationBarForNtpImpl(
!ColorUtils.inNightMode(getContext()),
/* useDefaultUrlBarAndUrlActionContainerAppearance= */ false);
} else {
// Restore the appearance of the real search box when transitioning from NTP to other
// pages.
updateLocationBarForNtpImpl(
/* statusIconBackgroundVisibility= */ false,
/* useDefaultUrlBarAndUrlActionContainerAppearance= */ true);
}
}
/**
* Update the appearance (logo background, search text's color and style) of the location bar
* based on the value provided.
*
* @param statusIconBackgroundVisibility The visibility of the status icon background.
* @param useDefaultUrlBarAndUrlActionContainerAppearance Whether to use the default typeface
* and color for the search text in the search box and use the default end margin for the
* url action container in the search box. If not we will use specific settings for NTP's
* un-focus state.
*/
private void updateLocationBarForNtpImpl(
boolean statusIconBackgroundVisibility,
boolean useDefaultUrlBarAndUrlActionContainerAppearance) {
mLocationBar.setStatusIconBackgroundVisibility(statusIconBackgroundVisibility);
mLocationBar.updateUrlBarHintTextColor(useDefaultUrlBarAndUrlActionContainerAppearance);
mLocationBar.updateUrlActionContainerEndMargin(
useDefaultUrlBarAndUrlActionContainerAppearance);
}
private void updateVisualsForLocationBarState() {
TraceEvent.begin("ToolbarPhone.updateVisualsForLocationBarState");
@VisualState int newVisualState = computeVisualState();
updateLocationBarForNtp(newVisualState, urlHasFocus());
if (newVisualState == VisualState.NEW_TAB_NORMAL && mHomeButton != null) {
mHomeButton.setAccessibilityTraversalBefore(R.id.toolbar_buttons);
} else {
mHomeButton.setAccessibilityTraversalBefore(View.NO_ID);
}
// 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)) {
TraceEvent.end("ToolbarPhone.updateVisualsForLocationBarState");
return;
} else if (mBrandColorTransitionAnimation != null
&& mBrandColorTransitionAnimation.isRunning()) {
mBrandColorTransitionAnimation.end();
}
boolean visualStateChanged = mVisualState != newVisualState;
@ColorInt
int currentPrimaryColor =
isLocationBarShownInGeneralNtp()
? mToolbarBackgroundColorForNtp
: getToolbarDataProvider().getPrimaryColor();
@ColorInt int themeColorForProgressBar = getProgressBarColor();
// 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
int visualState = isIncognitoBranded() ? VisualState.INCOGNITO : VisualState.NORMAL;
themeColorForProgressBar = getToolbarColorForVisualState(visualState);
}
if (mVisualState == VisualState.BRAND_COLOR && !visualStateChanged) {
boolean unfocusedLocationBarUsesTransparentBg =
!ColorUtils.shouldUseOpaqueTextboxBackground(currentPrimaryColor);
if (unfocusedLocationBarUsesTransparentBg != mUnfocusedLocationBarUsesTransparentBg) {
visualStateChanged = true;
} else {
updateToolbarBackgroundFromState(VisualState.BRAND_COLOR);
getProgressBar().setThemeColor(themeColorForProgressBar, isIncognitoBranded());
}
}
startLoadingPhaseFromNtpToWebpage(newVisualState);
mVisualState = newVisualState;
// Refresh the toolbar texture.
if ((mVisualState == VisualState.BRAND_COLOR || visualStateChanged)
&& mLayoutUpdater != null) {
mLayoutUpdater.run();
}
updateShadowVisibility();
updateUrlExpansionAnimation();
// This exception is to prevent early change of theme color when exiting the tab switcher
// since currently visual state does not map correctly to tab switcher state. See
// https://crbug.com/832594 for more info.
if (mTabSwitcherState != EXITING_TAB_SWITCHER) {
updateToolbarBackgroundFromState(mVisualState);
}
if (!visualStateChanged) {
if (!ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.TOOLBAR_PHONE_CLEANUP,
PARAM_REMOVE_REDUNDANT_ANIM_CALL,
PARAM_REMOVE_REDUNDANT_ANIM_CALL_DEFAULT_VAL)) {
if (mVisualState == VisualState.NEW_TAB_NORMAL) {
updateNtpTransitionAnimation();
} else {
resetNtpAnimationValues();
}
}
TraceEvent.end("ToolbarPhone.updateVisualsForLocationBarState");
return;
}
mUnfocusedLocationBarUsesTransparentBg = false;
getProgressBar().setThemeColor(themeColorForProgressBar, isIncognitoBranded());
if (mVisualState == VisualState.BRAND_COLOR) {
mUnfocusedLocationBarUsesTransparentBg =
!ColorUtils.shouldUseOpaqueTextboxBackground(currentPrimaryColor);
}
updateModernLocationBarColor(getLocationBarColorForToolbarColor(currentPrimaryColor));
mLocationBar.updateVisualsForState();
if (!ChromeFeatureList.getFieldTrialParamByFeatureAsBoolean(
ChromeFeatureList.TOOLBAR_PHONE_CLEANUP,
PARAM_REMOVE_REDUNDANT_ANIM_CALL,
PARAM_REMOVE_REDUNDANT_ANIM_CALL_DEFAULT_VAL)) {
// These are used to skip setting state unnecessarily while in the tab switcher.
boolean inOrEnteringStaticTab =
mTabSwitcherState == STATIC_TAB || mTabSwitcherState == EXITING_TAB_SWITCHER;
// 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();
}
}
getMenuButtonCoordinator().setVisibility(true);
TraceEvent.end("ToolbarPhone.updateVisualsForLocationBarState");
}
/**
* Initiates the loading phase when transitioning from the NTP to a webpage. When the old visual
* state is either VisualState.NEW_TAB_NORMAL or VisualState.NEW_TAB_SEARCH_ENGINE_NO_LOGO, and
* the new visual state updates to VisualState.NORMAL, and the current page is NTP, this
* indicates the beginning of the process of navigating from the New Tab Page to other webpages.
* This condition is unique because in other scenarios, the visual state does not change in this
* manner.
*/
private void startLoadingPhaseFromNtpToWebpage(@VisualState int newVisualState) {
boolean isStartLoadingPhaseFromNtpToWebpage =
(mVisualState == VisualState.NEW_TAB_NORMAL
|| mVisualState == VisualState.NEW_TAB_SEARCH_ENGINE_NO_LOGO)
&& newVisualState == VisualState.NORMAL;
boolean hasVisibleNtp = getToolbarDataProvider().getNewTabPageDelegate().wasShowingNtp();
if (isStartLoadingPhaseFromNtpToWebpage && hasVisibleNtp) {
mIsInLoadingPhaseFromNtpToWebpage = true;
}
}
@Override
public LocationBar getLocationBar() {
return mLocationBar;
}
private void initializeOptionalButton() {
if (mOptionalButtonCoordinator == null) {
ViewStub optionalButtonStub = findViewById(R.id.optional_button_stub);
if (optionalButtonStub == null) {
return;
}
optionalButtonStub.setLayoutResource(R.layout.optional_button_layout);
View optionalButton = optionalButtonStub.inflate();
BooleanSupplier isAnimationAllowedPredicate =
new BooleanSupplier() {
@Override
public boolean getAsBoolean() {
boolean transitioningAwayFromLocationBarInNtp =
getToolbarDataProvider()
.getNewTabPageDelegate()
.transitioningAwayFromLocationBar();
return mTabSwitcherState == STATIC_TAB
&& !mUrlFocusChangeInProgress
&& !urlHasFocus()
&& !transitioningAwayFromLocationBarInNtp;
}
};
assert mUserEducationHelper != null
: "initialize(...) must be called on the Toolbar first.";
mOptionalButtonCoordinator =
new OptionalButtonCoordinator(
optionalButton,
mUserEducationHelper,
/* transitionRoot= */ mToolbarButtonsContainer,
isAnimationAllowedPredicate,
mTrackerSupplier);
// Set the button's background to the same color as the URL bar background. This color
// is only used when showing dynamic actions.
mOptionalButtonCoordinator.setBackgroundColorFilter(mCurrentLocationBarColor);
// Set the button's foreground color to the same color as other toolbar icons. This
// color is not used on icons that don't support tinting (e.g. user profile pic).
mOptionalButtonCoordinator.setIconForegroundColor(getTint());
mOptionalButtonCoordinator.setOnBeforeHideTransitionCallback(
() -> mLayoutLocationBarWithoutExtraButton = true);
mOptionalButtonCoordinator.setTransitionStartedCallback(
transitionType -> {
mOptionalButtonAnimationRunning = true;
keepControlsShownForAnimation();
switch (transitionType) {
case TransitionType.COLLAPSING_ACTION_CHIP:
case TransitionType.HIDING:
mLayoutLocationBarWithoutExtraButton = true;
ViewUtils.requestLayout(
this,
"ToolbarPhone.initializeOptionalButton.mOptionalButton.setTransitionStartedCallback");
break;
case TransitionType.EXPANDING_ACTION_CHIP:
case TransitionType.SHOWING:
mDisableLocationBarRelayout = true;
break;
}
});
mOptionalButtonCoordinator.setTransitionFinishedCallback(
transitionType -> {
// If we are done expanding the transition chip then don't re-enable hiding
// browser controls, as we'll begin the collapse transition soon.
if (transitionType == TransitionType.EXPANDING_ACTION_CHIP) {
return;
}
mLayoutLocationBarWithoutExtraButton = false;
mDisableLocationBarRelayout = false;
mOptionalButtonAnimationRunning = false;
allowBrowserControlsHide();
if (mLayoutUpdater != null) mLayoutUpdater.run();
ViewUtils.requestLayout(
this,
"ToolbarPhone.initializeOptionalButton.mOptionalButton.setTransitionFinishedCallback");
});
}
}
@Override
void updateOptionalButton(ButtonData buttonData) {
mButtonData = buttonData;
if (mOptionalButtonCoordinator == null) {
initializeOptionalButton();
}
mOptionalButtonCoordinator.updateButton(buttonData);
}
@Override
protected void onMenuButtonDisabled() {
super.onMenuButtonDisabled();
// Menu button should always be enabled on ToolbarPhone.
assert false;
}
@Override
void hideOptionalButton() {
mButtonData = null;
if (mOptionalButtonCoordinator == null
|| mOptionalButtonCoordinator.getViewVisibility() == View.GONE
|| mLayoutLocationBarWithoutExtraButton) {
return;
}
mOptionalButtonCoordinator.hideButton();
}
@Override
public View getOptionalButtonViewForTesting() {
if (mOptionalButtonCoordinator != null) {
return mOptionalButtonCoordinator.getButtonViewForTesting();
}
return null;
}
/**
* Whether the menu button is visible. Used as a proxy for whether there are end toolbar
* buttons besides the optional button.
*/
private boolean isMenuButtonPresent() {
return getMenuButtonCoordinator().isVisible();
}
/**
* Custom drawable that allows sharing the NTP search box drawable between the toolbar and the
* NTP. This allows animations to continue as the drawable is switched between the two owning
* views.
*/
private static class NtpSearchBoxDrawable extends DrawableWrapperCompat {
private final Drawable.Callback mCallback;
private int mBoundsLeft;
private int mBoundsTop;
private int mBoundsRight;
private int mBoundsBottom;
private boolean mPendingBoundsUpdateFromToolbar;
private boolean mDrawnByNtp;
/**
* Constructs the NTP search box drawable.
*
* @param context The context used to inflate the drawable.
* @param callback The callback to be notified on changes ot the drawable.
*/
public NtpSearchBoxDrawable(Context context, Drawable.Callback callback) {
super(
AppCompatResources.getDrawable(
context, R.drawable.home_surface_search_box_background));
mCallback = callback;
setCallback(mCallback);
}
/** Mark that the pending bounds update is coming from the toolbar. */
void markPendingBoundsUpdateFromToolbar() {
mPendingBoundsUpdateFromToolbar = true;
}
/**
* Reset the bounds of the drawable to the last bounds received that was not marked from
* the toolbar.
*/
void resetBoundsToLastNonToolbar() {
setBounds(mBoundsLeft, mBoundsTop, mBoundsRight, mBoundsBottom);
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
if (!mPendingBoundsUpdateFromToolbar) {
mBoundsLeft = left;
mBoundsTop = top;
mBoundsRight = right;
mBoundsBottom = bottom;
mDrawnByNtp = true;
} else {
mDrawnByNtp = false;
}
mPendingBoundsUpdateFromToolbar = false;
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
// Ignore visibility changes. The NTP can toggle the visibility based on the scroll
// position of the page, so we simply ignore all of this as we expect the drawable to
// be visible at all times of the NTP.
return false;
}
@Override
public Callback getCallback() {
return mDrawnByNtp ? super.getCallback() : mCallback;
}
}
private void cancelAnimations() {
if (mUrlFocusLayoutAnimator != null && mUrlFocusLayoutAnimator.isRunning()) {
mUrlFocusLayoutAnimator.cancel();
}
if (mBrandColorTransitionAnimation != null && mBrandColorTransitionAnimation.isRunning()) {
mBrandColorTransitionAnimation.cancel();
}
}
/**
* Calculates the offset required for the focused LocationBar to appear as if it's still
* unfocused so it can animate to a focused state.
*
* @param hasFocus True if the LocationBar has focus, this will be true between the focus
* animation starting and the unfocus animation starting.
* @return The offset for the location bar when showing the dse icon.
*/
int getLocationBarOffsetForFocusAnimation(boolean hasFocus) {
StatusCoordinator statusCoordinator = mLocationBar.getStatusCoordinator();
if (statusCoordinator == null) return 0;
var profile = getToolbarDataProvider().getProfile();
if (profile == null
|| !SearchEngineUtils.getForProfile(profile).shouldShowSearchEngineLogo()) {
return 0;
}
// On non-NTP pages, there will always be an icon when unfocused.
if (!getToolbarDataProvider().getNewTabPageDelegate().isCurrentlyVisible()) return 0;
// This offset is only required when the focus animation is running.
if (!hasFocus) return 0;
// We're on the NTP with the fakebox showing.
// The value returned changes based on if the layout is LTR OR RTL.
// For LTR, the value is negative because we are making space on the left-hand side.
// For RTL, the value is positive because we are pushing the icon further to the
// right-hand side.
int offset = statusCoordinator.getStatusIconWidth() - getAdditionalOffsetForNtp();
return mLocationBar.isLayoutRtl() ? offset : -offset;
}
private int getAdditionalOffsetForNtp() {
return getResources().getDimensionPixelSize(R.dimen.fake_search_box_lateral_padding)
- getResources().getDimensionPixelSize(R.dimen.location_bar_start_padding);
}
@Override
public boolean isAnimationRunningForTesting() {
return mUrlFocusChangeInProgress
|| mBrandColorTransitionActive
|| mOptionalButtonAnimationRunning;
}
/**
* @param toolbarColorObserver The observer that observes toolbar color change.
*/
@Override
public void setToolbarColorObserver(@NonNull ToolbarColorObserver toolbarColorObserver) {
super.setToolbarColorObserver(toolbarColorObserver);
notifyToolbarColorChanged(mToolbarBackground.getColor());
}
@Override
public void onSuggestionDropdownScroll() {
mDropdownListScrolled = true;
updateToolbarAndLocationBarColor();
}
@Override
public void onSuggestionDropdownOverscrolledToTop() {
mDropdownListScrolled = false;
updateToolbarAndLocationBarColor();
}
private void updateToolbarAndLocationBarColor() {
@ColorInt
int toolbarDefaultColor = getToolbarDefaultColor(/* shouldUseFocusColor= */ false);
updateToolbarBackground(toolbarDefaultColor);
updateModernLocationBarColor(
getLocationBarDefaultColorForToolbarColor(
toolbarDefaultColor, /* shouldUseFocusColor= */ false));
}
private int calculateOnFocusHeightIncrease() {
return (int) (mBackgroundHeightIncreaseWhenFocus * mUrlFocusChangeFraction / 2);
}
@Override
public void onTransitionStart() {
setVisibility(View.VISIBLE);
mInLayoutTransition = true;
updateToolbarBackgroundFromState(mVisualState);
}
@Override
public void onTransitionEnd() {
mInLayoutTransition = false;
updateToolbarBackgroundFromState(mVisualState);
}
/**
* @return The height of the background drawable for the location bar.
*/
public int getLocationBarBackgroundHeightForTesting() {
return (mLocationBarBackgroundBounds.bottom + mLocationBarBackgroundNtpOffset.bottom)
- (mLocationBarBackgroundBounds.top + mLocationBarBackgroundNtpOffset.top);
}
void setNtpSearchBoxScrollFractionForTesting(float ntpSearchBoxScrollFraction) {
mNtpSearchBoxScrollFraction = ntpSearchBoxScrollFraction;
}
}