// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.toolbar;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.CommandLine;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.NavigationPopup;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.omnibox.LocationBarTablet;
import org.chromium.chrome.browser.partnercustomizations.HomepageManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.widget.TintedImageButton;
import org.chromium.ui.base.DeviceFormFactor;
import java.util.ArrayList;
import java.util.Collection;
/**
* The Toolbar object for Tablet screens.
*/
@SuppressLint("Instantiatable")
public class ToolbarTablet extends ToolbarLayout implements OnClickListener {
// The number of toolbar buttons that can be hidden at small widths (reload, back, forward).
public static final int HIDEABLE_BUTTON_COUNT = 3;
private TintedImageButton mHomeButton;
private TintedImageButton mBackButton;
private TintedImageButton mForwardButton;
private TintedImageButton mReloadButton;
private TintedImageButton mBookmarkButton;
private TintedImageButton mSaveOfflineButton;
private ImageButton mAccessibilitySwitcherButton;
private OnClickListener mBookmarkListener;
private OnClickListener mTabSwitcherListener;
private boolean mIsInTabSwitcherMode = false;
private boolean mShowTabStack;
private boolean mToolbarButtonsVisible;
private TintedImageButton[] mToolbarButtons;
private NavigationPopup mNavigationPopup;
private TabSwitcherDrawable mTabSwitcherButtonDrawable;
private TabSwitcherDrawable mTabSwitcherButtonDrawableLight;
private Boolean mUseLightColorAssets;
private LocationBarTablet mLocationBar;
private final int mStartPaddingWithButtons;
private final int mStartPaddingWithoutButtons;
private boolean mShouldAnimateButtonVisibilityChange;
private AnimatorSet mButtonVisibilityAnimators;
private NewTabPage mVisibleNtp;
/**
* Constructs a ToolbarTablet object.
* @param context The Context in which this View object is created.
* @param attrs The AttributeSet that was specified with this View.
*/
public ToolbarTablet(Context context, AttributeSet attrs) {
super(context, attrs);
mStartPaddingWithButtons = getResources().getDimensionPixelOffset(
R.dimen.tablet_toolbar_start_padding);
mStartPaddingWithoutButtons = getResources().getDimensionPixelOffset(
R.dimen.tablet_toolbar_start_padding_no_buttons);
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mLocationBar = (LocationBarTablet) findViewById(R.id.location_bar);
mHomeButton = (TintedImageButton) findViewById(R.id.home_button);
mBackButton = (TintedImageButton) findViewById(R.id.back_button);
mForwardButton = (TintedImageButton) findViewById(R.id.forward_button);
mReloadButton = (TintedImageButton) findViewById(R.id.refresh_button);
mShowTabStack = DeviceClassManager.isAccessibilityModeEnabled(getContext())
|| CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_TABLET_TAB_STACK);
mTabSwitcherButtonDrawable =
TabSwitcherDrawable.createTabSwitcherDrawable(getResources(), false);
mTabSwitcherButtonDrawableLight =
TabSwitcherDrawable.createTabSwitcherDrawable(getResources(), true);
mAccessibilitySwitcherButton = (ImageButton) findViewById(R.id.tab_switcher_button);
mAccessibilitySwitcherButton.setImageDrawable(mTabSwitcherButtonDrawable);
updateSwitcherButtonVisibility(mShowTabStack);
mBookmarkButton = (TintedImageButton) findViewById(R.id.bookmark_button);
mMenuButton = (TintedImageButton) findViewById(R.id.menu_button);
mMenuButtonWrapper.setVisibility(View.VISIBLE);
if (mAccessibilitySwitcherButton.getVisibility() == View.GONE
&& mMenuButtonWrapper.getVisibility() == View.GONE) {
ApiCompatibilityUtils.setPaddingRelative((View) mMenuButtonWrapper.getParent(), 0, 0,
getResources().getDimensionPixelSize(R.dimen.tablet_toolbar_end_padding), 0);
}
mSaveOfflineButton = (TintedImageButton) findViewById(R.id.save_offline_button);
// Initialize values needed for showing/hiding toolbar buttons when the activity size
// changes.
mShouldAnimateButtonVisibilityChange = false;
mToolbarButtonsVisible = true;
mToolbarButtons = new TintedImageButton[] {mBackButton, mForwardButton, mReloadButton};
}
/**
* Sets up key listeners after native initialization is complete, so that we can invoke
* native functions.
*/
@Override
public void onNativeLibraryReady() {
super.onNativeLibraryReady();
mLocationBar.onNativeLibraryReady();
mHomeButton.setOnClickListener(this);
mHomeButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
if (mBackButton.isFocusable()) {
return findViewById(R.id.back_button);
} else if (mForwardButton.isFocusable()) {
return findViewById(R.id.forward_button);
} else {
return findViewById(R.id.refresh_button);
}
}
@Override
public View getNextFocusBackward() {
return findViewById(R.id.menu_button);
}
});
mBackButton.setOnClickListener(this);
mBackButton.setLongClickable(true);
mBackButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
if (mForwardButton.isFocusable()) {
return findViewById(R.id.forward_button);
} else {
return findViewById(R.id.refresh_button);
}
}
@Override
public View getNextFocusBackward() {
if (mHomeButton.getVisibility() == VISIBLE) {
return findViewById(R.id.home_button);
} else {
return findViewById(R.id.menu_button);
}
}
});
mForwardButton.setOnClickListener(this);
mForwardButton.setLongClickable(true);
mForwardButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
return findViewById(R.id.refresh_button);
}
@Override
public View getNextFocusBackward() {
if (mBackButton.isFocusable()) {
return mBackButton;
} else if (mHomeButton.getVisibility() == VISIBLE) {
return findViewById(R.id.home_button);
} else {
return findViewById(R.id.menu_button);
}
}
});
mReloadButton.setOnClickListener(this);
mReloadButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
return findViewById(R.id.url_bar);
}
@Override
public View getNextFocusBackward() {
if (mForwardButton.isFocusable()) {
return mForwardButton;
} else if (mBackButton.isFocusable()) {
return mBackButton;
} else if (mHomeButton.getVisibility() == VISIBLE) {
return findViewById(R.id.home_button);
} else {
return findViewById(R.id.menu_button);
}
}
});
mAccessibilitySwitcherButton.setOnClickListener(this);
mBookmarkButton.setOnClickListener(this);
mMenuButton.setOnKeyListener(new KeyboardNavigationListener() {
@Override
public View getNextFocusForward() {
return getCurrentTabView();
}
@Override
public View getNextFocusBackward() {
return findViewById(R.id.url_bar);
}
@Override
protected boolean handleEnterKeyPress() {
return getMenuButtonHelper().onEnterKeyPress(mMenuButton);
}
});
if (HomepageManager.isHomepageEnabled(getContext())) {
mHomeButton.setVisibility(VISIBLE);
}
mSaveOfflineButton.setOnClickListener(this);
}
@Override
public boolean showContextMenuForChild(View originalView) {
if (mBackButton == originalView) {
// Display backwards navigation popup.
displayNavigationPopup(false, mBackButton);
return true;
} else if (mForwardButton == originalView) {
// Display forwards navigation popup.
displayNavigationPopup(true, mForwardButton);
return true;
}
return super.showContextMenuForChild(originalView);
}
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
// Ensure the the popup is not shown after resuming activity from background.
if (hasWindowFocus && mNavigationPopup != null) {
mNavigationPopup.dismiss();
mNavigationPopup = null;
}
super.onWindowFocusChanged(hasWindowFocus);
}
private void displayNavigationPopup(boolean isForward, View anchorView) {
Tab tab = getToolbarDataProvider().getTab();
if (tab == null || tab.getWebContents() == null) return;
mNavigationPopup = new NavigationPopup(tab.getProfile(), getContext(),
tab.getWebContents().getNavigationController(), isForward);
mNavigationPopup.setAnchorView(anchorView);
int menuWidth = getResources().getDimensionPixelSize(R.dimen.menu_width);
mNavigationPopup.setWidth(menuWidth);
if (mNavigationPopup.shouldBeShown()) mNavigationPopup.show();
}
@Override
public void onClick(View v) {
if (mHomeButton == v) {
openHomepage();
} else if (mBackButton == v) {
if (!back()) return;
RecordUserAction.record("MobileToolbarBack");
RecordUserAction.record("MobileTabClobbered");
} else if (mForwardButton == v) {
forward();
RecordUserAction.record("MobileToolbarForward");
RecordUserAction.record("MobileTabClobbered");
} else if (mReloadButton == v) {
stopOrReloadCurrentTab();
} else if (mBookmarkButton == v) {
if (mBookmarkListener != null) {
mBookmarkListener.onClick(mBookmarkButton);
RecordUserAction.record("MobileToolbarToggleBookmark");
}
} else if (mAccessibilitySwitcherButton == v) {
if (mTabSwitcherListener != null) {
cancelAppMenuUpdateBadgeAnimation();
mTabSwitcherListener.onClick(mAccessibilitySwitcherButton);
}
} else if (mSaveOfflineButton == v) {
DownloadUtils.downloadOfflinePage(getContext(), getToolbarDataProvider().getTab());
RecordUserAction.record("MobileToolbarDownloadPage");
}
}
private void updateSwitcherButtonVisibility(boolean enabled) {
mAccessibilitySwitcherButton.setVisibility(mShowTabStack || enabled
? View.VISIBLE : View.GONE);
}
@Override
public boolean isReadyForTextureCapture() {
return !urlHasFocus();
}
@Override
public void onTabOrModelChanged() {
super.onTabOrModelChanged();
boolean incognito = isIncognito();
if (mUseLightColorAssets == null || mUseLightColorAssets != incognito) {
setBackgroundResource(incognito
? R.color.incognito_primary_color : R.color.default_primary_color);
mMenuButton.setTint(incognito ? mLightModeTint : mDarkModeTint);
mHomeButton.setTint(incognito ? mLightModeTint : mDarkModeTint);
mBackButton.setTint(incognito ? mLightModeTint : mDarkModeTint);
mForwardButton.setTint(incognito ? mLightModeTint : mDarkModeTint);
mSaveOfflineButton.setTint(incognito ? mLightModeTint : mDarkModeTint);
if (incognito) {
mLocationBar.getContainerView().getBackground().setAlpha(
ToolbarPhone.LOCATION_BAR_TRANSPARENT_BACKGROUND_ALPHA);
} else {
mLocationBar.getContainerView().getBackground().setAlpha(255);
}
mAccessibilitySwitcherButton.setImageDrawable(
incognito ? mTabSwitcherButtonDrawableLight : mTabSwitcherButtonDrawable);
mLocationBar.updateVisualsForState();
if (mShowMenuBadge) {
setAppMenuUpdateBadgeDrawable(incognito);
}
mUseLightColorAssets = incognito;
}
updateNtp();
}
/**
* Called when the currently visible New Tab Page changes.
*/
private void updateNtp() {
NewTabPage newVisibleNtp = getToolbarDataProvider().getNewTabPageForCurrentTab();
if (mVisibleNtp == newVisibleNtp) return;
if (mVisibleNtp != null) {
mVisibleNtp.setSearchBoxScrollListener(null);
}
mVisibleNtp = newVisibleNtp;
if (mVisibleNtp != null) {
mVisibleNtp.setSearchBoxScrollListener(new NewTabPage.OnSearchBoxScrollListener() {
@Override
public void onNtpScrollChanged(float scrollPercentage) {
// Fade the search box out in the first 40% of the scrolling transition.
float alpha = Math.max(1f - scrollPercentage * 2.5f, 0f);
mVisibleNtp.setSearchBoxAlpha(alpha);
mVisibleNtp.setSearchProviderLogoAlpha(alpha);
}
});
}
}
@Override
protected void onTabContentViewChanged() {
super.onTabContentViewChanged();
updateNtp();
}
@Override
public void updateButtonVisibility() {
mLocationBar.updateButtonVisibility();
}
@Override
protected void updateBackButtonVisibility(boolean canGoBack) {
boolean enableButton = canGoBack && !mIsInTabSwitcherMode;
mBackButton.setEnabled(enableButton);
mBackButton.setFocusable(enableButton);
}
@Override
protected void updateForwardButtonVisibility(boolean canGoForward) {
boolean enableButton = canGoForward && !mIsInTabSwitcherMode;
mForwardButton.setEnabled(enableButton);
mForwardButton.setFocusable(enableButton);
}
@Override
protected void updateReloadButtonVisibility(boolean isReloading) {
if (isReloading) {
mReloadButton.setImageResource(R.drawable.btn_close);
mReloadButton.setContentDescription(getContext().getString(
R.string.accessibility_btn_stop_loading));
} else {
mReloadButton.setImageResource(R.drawable.btn_toolbar_reload);
mReloadButton.setContentDescription(getContext().getString(
R.string.accessibility_btn_refresh));
}
mReloadButton.setTint(isIncognito() ? mLightModeTint : mDarkModeTint);
mReloadButton.setEnabled(!mIsInTabSwitcherMode);
}
@Override
protected void updateBookmarkButton(boolean isBookmarked, boolean editingAllowed) {
if (isBookmarked) {
mBookmarkButton.setImageResource(R.drawable.btn_star_filled);
// Non-incognito mode shows a blue filled star.
mBookmarkButton.setTint(isIncognito()
? mLightModeTint
: ApiCompatibilityUtils.getColorStateList(
getResources(), R.color.blue_mode_tint));
mBookmarkButton.setContentDescription(getContext().getString(
R.string.edit_bookmark));
} else {
mBookmarkButton.setImageResource(R.drawable.btn_star);
mBookmarkButton.setTint(isIncognito() ? mLightModeTint : mDarkModeTint);
mBookmarkButton.setContentDescription(getContext().getString(
R.string.accessibility_menu_bookmark));
}
mBookmarkButton.setEnabled(editingAllowed);
}
@Override
protected void setTabSwitcherMode(
boolean inTabSwitcherMode, boolean showToolbar, boolean delayAnimation) {
if (mShowTabStack && inTabSwitcherMode) {
mIsInTabSwitcherMode = true;
mBackButton.setEnabled(false);
mForwardButton.setEnabled(false);
mReloadButton.setEnabled(false);
mLocationBar.getContainerView().setVisibility(View.INVISIBLE);
if (mShowMenuBadge) {
mMenuBadge.setVisibility(View.GONE);
setMenuButtonContentDescription(false);
}
} else {
mIsInTabSwitcherMode = false;
mLocationBar.getContainerView().setVisibility(View.VISIBLE);
if (mShowMenuBadge) {
setAppMenuUpdateBadgeToVisible(false);
}
}
}
@Override
protected void updateTabCountVisuals(int numberOfTabs) {
mAccessibilitySwitcherButton.setContentDescription(
getResources().getQuantityString(
R.plurals.accessibility_toolbar_btn_tabswitcher_toggle,
numberOfTabs, numberOfTabs));
mTabSwitcherButtonDrawable.updateForTabCount(numberOfTabs, isIncognito());
mTabSwitcherButtonDrawableLight.updateForTabCount(numberOfTabs, isIncognito());
}
@Override
public void onAccessibilityStatusChanged(boolean enabled) {
mShowTabStack = enabled || CommandLine.getInstance().hasSwitch(
ChromeSwitches.ENABLE_TABLET_TAB_STACK);
updateSwitcherButtonVisibility(enabled);
}
@Override
public void setBookmarkClickHandler(OnClickListener listener) {
mBookmarkListener = listener;
}
@Override
public void setOnTabSwitcherClickHandler(OnClickListener listener) {
mTabSwitcherListener = listener;
}
@Override
protected void onHomeButtonUpdate(boolean homeButtonEnabled) {
mHomeButton.setVisibility(homeButtonEnabled ? VISIBLE : GONE);
}
@Override
public LocationBar getLocationBar() {
return mLocationBar;
}
@Override
public void showAppMenuUpdateBadge() {
super.showAppMenuUpdateBadge();
if (!mIsInTabSwitcherMode) {
if (mUseLightColorAssets) {
setAppMenuUpdateBadgeDrawable(mUseLightColorAssets);
}
setAppMenuUpdateBadgeToVisible(true);
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// After the first layout, button visibility changes should be animated. On the first
// layout, the button visibility shouldn't be animated because the visibility may be
// changing solely because Chrome was launched into multi-window.
mShouldAnimateButtonVisibilityChange = true;
super.onLayout(changed, left, top, right, bottom);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Hide or show toolbar buttons if needed. With the introduction of multi-window on
// Android N, the Activity can be < 600dp, in which case the toolbar buttons need to be
// moved into the menu so that the location bar is usable. The buttons must be shown
// in onMeasure() so that the location bar gets measured and laid out correctly.
setToolbarButtonsVisible(MeasureSpec.getSize(widthMeasureSpec)
>= DeviceFormFactor.getMinimumTabletWidthPx(getContext()));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void setToolbarButtonsVisible(boolean visible) {
if (mToolbarButtonsVisible == visible) return;
mToolbarButtonsVisible = visible;
if (mShouldAnimateButtonVisibilityChange) {
runToolbarButtonsVisibilityAnimation(visible);
} else {
for (TintedImageButton button : mToolbarButtons) {
button.setVisibility(visible ? View.VISIBLE : View.GONE);
}
mLocationBar.setShouldShowButtonsWhenUnfocused(visible);
setStartPaddingBasedOnButtonVisibility(visible);
}
}
/**
* Sets the toolbar start padding based on whether the buttons are visible.
* @param buttonsVisible Whether the toolbar buttons are visible.
*/
private void setStartPaddingBasedOnButtonVisibility(boolean buttonsVisible) {
buttonsVisible = buttonsVisible || mHomeButton.getVisibility() == View.VISIBLE;
ApiCompatibilityUtils.setPaddingRelative(this,
buttonsVisible ? mStartPaddingWithButtons : mStartPaddingWithoutButtons,
getPaddingTop(),
ApiCompatibilityUtils.getPaddingEnd(this),
getPaddingBottom());
}
/**
* @return The difference in start padding when the buttons are visible and when they are not
* visible.
*/
public int getStartPaddingDifferenceForButtonVisibilityAnimation() {
// If the home button is visible then the padding doesn't change.
return mHomeButton.getVisibility() == View.VISIBLE ? 0
: mStartPaddingWithButtons - mStartPaddingWithoutButtons;
}
private void runToolbarButtonsVisibilityAnimation(boolean visible) {
if (mButtonVisibilityAnimators != null) mButtonVisibilityAnimators.cancel();
mButtonVisibilityAnimators = visible ? buildShowToolbarButtonsAnimation()
: buildHideToolbarButtonsAnimation();
mButtonVisibilityAnimators.start();
}
private AnimatorSet buildShowToolbarButtonsAnimation() {
Collection<Animator> animators = new ArrayList<>();
// Create animators for all of the toolbar buttons.
for (TintedImageButton button : mToolbarButtons) {
animators.add(mLocationBar.createShowButtonAnimator(button));
}
// Add animators for location bar.
animators.addAll(mLocationBar.getShowButtonsWhenUnfocusedAnimators(
getStartPaddingDifferenceForButtonVisibilityAnimation()));
AnimatorSet set = new AnimatorSet();
set.playTogether(animators);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
for (TintedImageButton button : mToolbarButtons) {
button.setVisibility(View.VISIBLE);
}
// Set the padding at the start of the animation so the toolbar buttons don't jump
// when the animation ends.
setStartPaddingBasedOnButtonVisibility(true);
}
@Override
public void onAnimationEnd(Animator animation) {
mButtonVisibilityAnimators = null;
}
});
return set;
}
private AnimatorSet buildHideToolbarButtonsAnimation() {
Collection<Animator> animators = new ArrayList<>();
// Create animators for all of the toolbar buttons.
for (TintedImageButton button : mToolbarButtons) {
animators.add(mLocationBar.createHideButtonAnimator(button));
}
// Add animators for location bar.
animators.addAll(mLocationBar.getHideButtonsWhenUnfocusedAnimators(
getStartPaddingDifferenceForButtonVisibilityAnimation()));
AnimatorSet set = new AnimatorSet();
set.playTogether(animators);
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Only set end visibility and alpha if the animation is ending because it's
// completely finished and not because it was canceled.
if (mToolbarButtons[0].getAlpha() == 0.f) {
for (TintedImageButton button : mToolbarButtons) {
button.setVisibility(View.GONE);
button.setAlpha(1.f);
}
// Set the padding at the end of the animation so the toolbar buttons don't jump
// when the animation starts.
setStartPaddingBasedOnButtonVisibility(false);
}
mButtonVisibilityAnimators = null;
}
});
return set;
}
}