Commit fbc145ee authored by Patrick Noland's avatar Patrick Noland Committed by Commit Bot

[ToolbarMVC] Reimplement MenuButton internals to follow MVC

This adds the familiar MVC pieces, in addition to the existing Coordinator and View: a Mediator, a ViewBinder, and a Properties class. Logic is moved out of the MenuButton and MenuButtonCoordinator to the Mediator. Of note, there are two compound properties for combinations of properties that need to be set together:
* show/hide badge and shouldAnimate
* useLight and ColorStateList

Bug: 1086676
Change-Id: I12fb7aa1e21b78b0cddf6b72c322e3c030fae78e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2406378
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#809896}
parent e2b1a235
......@@ -1574,6 +1574,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/toolbar/load_progress/LoadProgressViewBinder.java",
"java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButton.java",
"java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinator.java",
"java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediator.java",
"java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonProperties.java",
"java/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonViewBinder.java",
"java/src/org/chromium/chrome/browser/toolbar/top/ActionModeController.java",
"java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbar.java",
"java/src/org/chromium/chrome/browser/toolbar/top/CustomTabToolbarAnimationDelegate.java",
......
......@@ -235,6 +235,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/toolbar/LocationBarModelTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/ToolbarTabControllerImplTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonCoordinatorTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonMediatorTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/menu_button/MenuButtonTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/top/OptionalBrowsingModeButtonControllerTest.java",
"junit/src/org/chromium/chrome/browser/toolbar/top/StartSurfaceToolbarMediatorUnitTest.java",
......
......@@ -25,7 +25,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.view.ViewCompat;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.ThemeColorProvider.TintObserver;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper.MenuButtonState;
......@@ -51,12 +50,10 @@ public class MenuButton extends FrameLayout implements TintObserver {
private boolean mHighlightingMenu;
private PulseDrawable mHighlightDrawable;
private boolean mSuppressAppMenuUpdateBadge;
private AnimatorSet mMenuBadgeAnimatorSet;
private boolean mIsMenuBadgeAnimationRunning;
/** A provider that notifies components when the theme color changes.*/
private ThemeColorProvider mThemeColorProvider;
private BitmapDrawable mMenuImageButtonAnimationDrawable;
private BitmapDrawable mUpdateBadgeAnimationDrawable;
......@@ -98,7 +95,6 @@ public class MenuButton extends FrameLayout implements TintObserver {
if (mUpdateBadgeView == null) return;
mUpdateBadgeView.setVisibility(visible ? View.VISIBLE : View.GONE);
if (visible) updateImageResources();
updateContentDescription(visible);
}
@Override
......@@ -147,14 +143,12 @@ public class MenuButton extends FrameLayout implements TintObserver {
* Show the update badge on the app menu button.
* @param animate Whether to animate the showing of the update badge.
*/
public void showAppMenuUpdateBadgeIfAvailable(boolean animate) {
if (mUpdateBadgeView == null || mMenuImageButton == null || mSuppressAppMenuUpdateBadge
|| !isBadgeAvailable()) {
void showAppMenuUpdateBadge(boolean animate) {
if (mUpdateBadgeView == null || mMenuImageButton == null) {
return;
}
updateImageResources();
updateContentDescription(true);
if (!animate || mIsMenuBadgeAnimationRunning) {
setUpdateBadgeVisibility(true);
return;
......@@ -190,9 +184,8 @@ public class MenuButton extends FrameLayout implements TintObserver {
* Remove the update badge on the app menu button.
* @param animate Whether to animate the hiding of the update badge.
*/
public void removeAppMenuUpdateBadge(boolean animate) {
void removeAppMenuUpdateBadge(boolean animate) {
if (mUpdateBadgeView == null || !isShowingAppMenuUpdateBadge()) return;
updateContentDescription(false);
if (!animate) {
setUpdateBadgeVisibility(false);
......@@ -228,52 +221,22 @@ public class MenuButton extends FrameLayout implements TintObserver {
mMenuBadgeAnimatorSet.start();
}
/**
* @param suppress Whether to prevent the update badge from being show. This is currently only
* used to prevent the badge from being shown in the tablet tab switcher.
*/
public void setAppMenuUpdateBadgeSuppressed(boolean suppress) {
mSuppressAppMenuUpdateBadge = suppress;
if (mSuppressAppMenuUpdateBadge) {
removeAppMenuUpdateBadge(false);
} else {
showAppMenuUpdateBadgeIfAvailable(false);
}
}
/**
* @return Whether the update badge is showing.
*/
public boolean isShowingAppMenuUpdateBadge() {
boolean isShowingAppMenuUpdateBadge() {
return mUpdateBadgeView != null && mUpdateBadgeView.getVisibility() == View.VISIBLE;
}
private static boolean isBadgeAvailable() {
return UpdateMenuItemHelper.getInstance().getUiState().buttonState != null;
}
/**
* Sets the content description for the menu button.
* @param isUpdateBadgeVisible Whether the update menu badge is visible.
*/
private void updateContentDescription(boolean isUpdateBadgeVisible) {
if (isUpdateBadgeVisible) {
MenuButtonState buttonState =
UpdateMenuItemHelper.getInstance().getUiState().buttonState;
assert buttonState != null : "No button state when trying to show the badge.";
mMenuImageButton.setContentDescription(
getResources().getString(buttonState.menuContentDescription));
} else {
mMenuImageButton.setContentDescription(
getResources().getString(R.string.accessibility_toolbar_btn_menu));
}
void updateContentDescription(String description) {
mMenuImageButton.setContentDescription(description);
}
/**
* Sets the menu button's background depending on whether or not we are highlighting and whether
* or not we are using light or dark assets.
*/
public void setMenuButtonHighlightDrawable() {
private void updateMenuButtonHighlightDrawable() {
// Return if onFinishInflate didn't finish
if (mMenuImageButton == null) return;
......@@ -294,15 +257,9 @@ public class MenuButton extends FrameLayout implements TintObserver {
}
}
public void setMenuButtonHighlight(boolean highlight) {
void setMenuButtonHighlight(boolean highlight) {
mHighlightingMenu = highlight;
setMenuButtonHighlightDrawable();
}
public void setThemeColorProvider(ThemeColorProvider themeColorProvider) {
mThemeColorProvider = themeColorProvider;
mThemeColorProvider.addTintObserver(this);
onTintChanged(themeColorProvider.getTint(), themeColorProvider.useLight());
updateMenuButtonHighlightDrawable();
}
/**
......@@ -322,13 +279,7 @@ public class MenuButton extends FrameLayout implements TintObserver {
ApiCompatibilityUtils.setImageTintList(mMenuImageButton, tintList);
mUseLightDrawables = useLight;
updateImageResources();
}
public void destroy() {
if (mThemeColorProvider != null) {
mThemeColorProvider.removeTintObserver(this);
mThemeColorProvider = null;
}
updateMenuButtonHighlightDrawable();
}
@VisibleForTesting
......
......@@ -5,54 +5,39 @@
package org.chromium.chrome.browser.toolbar.menu_button;
import android.app.Activity;
import android.content.res.ColorStateList;
import android.view.View.OnKeyListener;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ShowBadgeProperty;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ThemeProperty;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.ui.UiUtils;
import org.chromium.ui.util.TokenHolder;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
/**
* Root component for the app menu button on the toolbar. Owns the MenuButton view and handles
* changes to its visual state, e.g. showing/hiding the app update badge.
*/
public class MenuButtonCoordinator implements AppMenuObserver {
public class MenuButtonCoordinator {
public interface SetFocusFunction {
void setFocus(boolean focus, int reason);
}
private @Nullable AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
private AppMenuButtonHelper mAppMenuButtonHelper;
private ObservableSupplierImpl<AppMenuButtonHelper> mAppMenuButtonHelperSupplier;
private AppMenuHandler mAppMenuHandler;
private final BrowserStateBrowserControlsVisibilityDelegate mControlsVisibilityDelegate;
private final Activity mActivity;
private int mFullscreenMenuToken = TokenHolder.INVALID_TOKEN;
private int mFullscreenHighlightToken = TokenHolder.INVALID_TOKEN;
private final SetFocusFunction mSetUrlBarFocusFunction;
private Runnable mRequestRenderRunnable;
private Runnable mUpdateStateChangedListener;
private final boolean mShouldShowAppUpdateBadge;
private Supplier<Boolean> mIsInOverviewModeSupplier;
private ThemeColorProvider mThemeColorProvider;
private final PropertyModel mPropertyModel;
private MenuButtonMediator mMediator;
private AppMenuButtonHelper mAppMenuButtonHelper;
private MenuButton mMenuButton;
private PropertyModelChangeProcessor mChangeProcessor;
/**
* @param appMenuCoordinatorSupplier Supplier for the AppMenuCoordinator, which owns all other
......@@ -75,18 +60,27 @@ public class MenuButtonCoordinator implements AppMenuObserver {
Runnable requestRenderRunnable, boolean shouldShowAppUpdateBadge,
Supplier<Boolean> isInOverviewModeSupplier, ThemeColorProvider themeColorProvider,
@IdRes int menuButtonId) {
mControlsVisibilityDelegate = controlsVisibilityDelegate;
mActivity = activity;
mSetUrlBarFocusFunction = setUrlBarFocusFunction;
appMenuCoordinatorSupplier.onAvailable(this::onAppMenuInitialized);
mRequestRenderRunnable = requestRenderRunnable;
mShouldShowAppUpdateBadge = shouldShowAppUpdateBadge;
mIsInOverviewModeSupplier = isInOverviewModeSupplier;
mMenuButton = mActivity.findViewById(menuButtonId);
mAppMenuButtonHelperSupplier = new ObservableSupplierImpl<>();
mThemeColorProvider = themeColorProvider;
mPropertyModel = new PropertyModel.Builder(MenuButtonProperties.ALL_KEYS)
.with(MenuButtonProperties.SHOW_UPDATE_BADGE,
new ShowBadgeProperty(false, false))
.with(MenuButtonProperties.THEME,
new ThemeProperty(themeColorProvider.getTint(),
themeColorProvider.useLight()))
.with(MenuButtonProperties.IS_VISIBLE, true)
.build();
mMediator = new MenuButtonMediator(mPropertyModel, shouldShowAppUpdateBadge,
()
-> mActivity.isFinishing() || mActivity.isDestroyed(),
requestRenderRunnable, themeColorProvider, isInOverviewModeSupplier,
controlsVisibilityDelegate, setUrlBarFocusFunction, appMenuCoordinatorSupplier,
mActivity.getResources());
mMediator.getMenuButtonHelperSupplier().addObserver(
(helper) -> mAppMenuButtonHelper = helper);
if (mMenuButton != null) {
mMenuButton.setThemeColorProvider(themeColorProvider);
mChangeProcessor = PropertyModelChangeProcessor.create(
mPropertyModel, mMenuButton, new MenuButtonViewBinder());
}
}
......@@ -96,11 +90,8 @@ public class MenuButtonCoordinator implements AppMenuObserver {
* @param isLoading Whether the current page is loading.
*/
public void updateReloadingState(boolean isLoading) {
if (mMenuButton == null || mAppMenuPropertiesDelegate == null || mAppMenuHandler == null) {
return;
}
mAppMenuPropertiesDelegate.loadingStateChanged(isLoading);
mAppMenuHandler.menuItemContentChanged(R.id.icon_row_menu_id);
if (mMediator == null) return;
mMediator.updateReloadingState(isLoading);
}
/**
......@@ -122,10 +113,8 @@ public class MenuButtonCoordinator implements AppMenuObserver {
assert mMenuButton == null;
assert menuButton != null;
mMenuButton = menuButton;
if (mAppMenuButtonHelper != null) {
mMenuButton.setAppMenuButtonHelper(mAppMenuButtonHelper);
}
mMenuButton.setThemeColorProvider(mThemeColorProvider);
mChangeProcessor = PropertyModelChangeProcessor.create(
mPropertyModel, menuButton, new MenuButtonViewBinder());
}
/**
......@@ -152,24 +141,12 @@ public class MenuButtonCoordinator implements AppMenuObserver {
return mMenuButton;
}
/**
* Set the tint list on the underlying MenuButton view. Present for legacy reasons only; don't
* add new usages.
*/
@Deprecated
public void setImageTintList(ColorStateList colorStateList) {
// TODO(https://crbug.com/1086676): Remove the need for these null checks and replace with
// an assert that the MenuButtonCoordinator isn't destroyed.
if (mMenuButton == null) return;
ApiCompatibilityUtils.setImageTintList(mMenuButton.getImageButton(), colorStateList);
}
/**
* @param isClickable Whether the underlying MenuButton view should be clickable.
*/
public void setClickable(boolean isClickable) {
if (mMenuButton == null) return;
mMenuButton.setClickable(isClickable);
if (mMediator == null) return;
mMediator.setClickable(isClickable);
}
/**
......@@ -182,20 +159,18 @@ public class MenuButtonCoordinator implements AppMenuObserver {
}
public void destroy() {
if (mAppMenuButtonHelper != null) {
mAppMenuHandler.removeObserver(this);
mAppMenuButtonHelper = null;
if (mMediator != null) {
mMediator.destroy();
mMediator = null;
}
if (mUpdateStateChangedListener != null) {
UpdateMenuItemHelper.getInstance().unregisterObserver(mUpdateStateChangedListener);
mUpdateStateChangedListener = null;
if (mChangeProcessor != null) {
mChangeProcessor.destroy();
mChangeProcessor = null;
}
if (mMenuButton != null) {
mMenuButton.destroy();
mMenuButton = null;
}
mAppMenuButtonHelper = null;
}
/**
......@@ -203,15 +178,14 @@ public class MenuButtonCoordinator implements AppMenuObserver {
* dependencies that require native, e.g. the UpdateMenuItemHelper.
*/
public void onNativeInitialized() {
if (mShouldShowAppUpdateBadge) {
mUpdateStateChangedListener = this::updateStateChanged;
UpdateMenuItemHelper.getInstance().registerObserver(mUpdateStateChangedListener);
}
if (mMediator == null) return;
mMediator.onNativeInitialized();
}
@Nullable
public ObservableSupplier<AppMenuButtonHelper> getMenuButtonHelperSupplier() {
return mAppMenuButtonHelperSupplier;
if (mMediator == null) return null;
return mMediator.getMenuButtonHelperSupplier();
}
/**
......@@ -219,110 +193,17 @@ public class MenuButtonCoordinator implements AppMenuObserver {
* @param isSuppressed
*/
public void setAppMenuUpdateBadgeSuppressed(boolean isSuppressed) {
if (mMenuButton == null) return;
mMenuButton.setAppMenuUpdateBadgeSuppressed(isSuppressed);
if (mMediator == null) return;
mMediator.setAppMenuUpdateBadgeSuppressed(isSuppressed);
}
/**
* Set the visibility of the MenuButton controlled by this coordinator.
* @param visibility Visibility state flag, e.g. GONE or VISIBLE.
*/
public void setVisibility(int visibility) {
if (mMenuButton == null) return;
mMenuButton.setVisibility(visibility);
}
/**
* @see MenuButton#setMenuButtonHighlightDrawable().
* @param visible Visibility state, true for visible and false for hidden.
*/
public void setMenuButtonHighlightDrawable() {
if (mMenuButton == null) return;
mMenuButton.setMenuButtonHighlightDrawable();
}
@Override
public void onMenuVisibilityChanged(boolean isVisible) {
if (isVisible) {
// Defocus here to avoid handling focus in multiple places, e.g., when the
// forward button is pressed. (see crbug.com/414219)
mSetUrlBarFocusFunction.setFocus(false, LocationBar.OmniboxFocusReason.UNFOCUS);
if (!mIsInOverviewModeSupplier.get() && isShowingAppMenuUpdateBadge()) {
// The app menu badge should be removed the first time the menu is opened.
mMenuButton.removeAppMenuUpdateBadge(true);
mRequestRenderRunnable.run();
}
mFullscreenMenuToken =
mControlsVisibilityDelegate.showControlsPersistentAndClearOldToken(
mFullscreenMenuToken);
} else {
mControlsVisibilityDelegate.releasePersistentShowingToken(mFullscreenMenuToken);
}
if (isVisible && mMenuButton != null && mMenuButton.isShowingAppMenuUpdateBadge()) {
UpdateMenuItemHelper.getInstance().onMenuButtonClicked();
}
}
@Override
public void onMenuHighlightChanged(boolean isHighlighting) {
if (mMenuButton != null) {
mMenuButton.setMenuButtonHighlight(isHighlighting);
}
if (isHighlighting) {
mFullscreenHighlightToken =
mControlsVisibilityDelegate.showControlsPersistentAndClearOldToken(
mFullscreenHighlightToken);
} else {
mControlsVisibilityDelegate.releasePersistentShowingToken(mFullscreenHighlightToken);
}
}
/**
* Called when the app menu and related properties delegate are available.
*
* @param appMenuCoordinator The coordinator for interacting with the menu.
*/
private void onAppMenuInitialized(AppMenuCoordinator appMenuCoordinator) {
assert mAppMenuHandler == null;
AppMenuHandler appMenuHandler = appMenuCoordinator.getAppMenuHandler();
mAppMenuHandler = appMenuHandler;
mAppMenuHandler.addObserver(this);
mAppMenuButtonHelper = mAppMenuHandler.createAppMenuButtonHelper();
mAppMenuButtonHelper.setOnAppMenuShownListener(
() -> { RecordUserAction.record("MobileToolbarShowMenu"); });
if (mMenuButton != null) {
mMenuButton.setAppMenuButtonHelper(mAppMenuButtonHelper);
}
mAppMenuButtonHelperSupplier.set(mAppMenuButtonHelper);
mAppMenuPropertiesDelegate = appMenuCoordinator.getAppMenuPropertiesDelegate();
}
/**
* @return Whether the badge is showing (either in the toolbar).
*/
private boolean isShowingAppMenuUpdateBadge() {
return mMenuButton != null && mMenuButton.isShowingAppMenuUpdateBadge();
}
@VisibleForTesting
void updateStateChanged() {
if (mMenuButton == null || mActivity.isFinishing() || mActivity.isDestroyed()
|| !mShouldShowAppUpdateBadge) {
return;
}
UpdateMenuItemHelper.MenuButtonState buttonState =
UpdateMenuItemHelper.getInstance().getUiState().buttonState;
if (buttonState != null) {
mMenuButton.showAppMenuUpdateBadgeIfAvailable(true);
mRequestRenderRunnable.run();
} else {
mMenuButton.removeAppMenuUpdateBadge(false);
}
public void setVisibility(boolean visible) {
if (mMediator == null) return;
mMediator.setVisibility(visible);
}
}
// Copyright 2020 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.menu_button;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import androidx.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.base.supplier.ObservableSupplier;
import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.OneshotSupplier;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper.MenuButtonState;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator.SetFocusFunction;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ShowBadgeProperty;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ThemeProperty;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.util.TokenHolder;
/**
* Mediator for the MenuButton. Listens for MenuButton state changes and drives corresponding
* changes to the property model that backs the MenuButton view.
*/
class MenuButtonMediator implements AppMenuObserver {
private OneshotSupplier<AppMenuCoordinator> mAppMenuCoordinatorSupplier;
private Callback<AppMenuCoordinator> mAppMenuCoordinatorSupplierObserver;
private @Nullable AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
private AppMenuButtonHelper mAppMenuButtonHelper;
private ObservableSupplierImpl<AppMenuButtonHelper> mAppMenuButtonHelperSupplier;
private AppMenuHandler mAppMenuHandler;
private final BrowserStateBrowserControlsVisibilityDelegate mControlsVisibilityDelegate;
private final SetFocusFunction mSetUrlBarFocusFunction;
private final PropertyModel mPropertyModel;
private final Runnable mRequestRenderRunnable;
private final ThemeColorProvider mThemeColorProvider;
private boolean mShouldShowAppUpdateBadge;
private Runnable mUpdateStateChangedListener;
private Supplier<Boolean> mIsActivityFinishingSupplier;
private int mFullscreenMenuToken = TokenHolder.INVALID_TOKEN;
private int mFullscreenHighlightToken = TokenHolder.INVALID_TOKEN;
private Supplier<Boolean> mIsInOverviewModeSupplier;
private boolean mSuppressAppMenuUpdateBadge;
private Resources mResources;
/**
* @param appMenuCoordinatorSupplier Supplier for the AppMenuCoordinator, which owns all other
* app menu MVC components.
* @param controlsVisibilityDelegate Delegate for forcing persistent display of browser
* controls.
* @param setUrlBarFocusFunction Function that allows setting focus on the url bar.
* @param requestRenderRunnable Runnable that requests a re-rendering of the compositor view
* containing the app menu button.
* @param shouldShowAppUpdateBadge Whether the app menu update badge should be shown if there is
* a pending update.
* @param isInOverviewModeSupplier Supplier of overview mode state.
* @param themeColorProvider Provider of theme color changes.
* @param resources Resources object to use to obtain, e.g. localized strings.
* @param shouldShowAppUpdateBadge Whether the "update available" badge should ever be shown.
* @param isActivityFinishingSupplier Supplier for knowing if the embedding activity is in the
* process of finishing or has already been destroyed.
* @param propertyModel Model to write property changes to.
*/
MenuButtonMediator(PropertyModel propertyModel, boolean shouldShowAppUpdateBadge,
Supplier<Boolean> isActivityFinishingSupplier, Runnable requestRenderRunnable,
ThemeColorProvider themeColorProvider, Supplier<Boolean> isInOverviewModeSupplier,
BrowserStateBrowserControlsVisibilityDelegate controlsVisibilityDelegate,
SetFocusFunction setUrlBarFocusFunction,
OneshotSupplier<AppMenuCoordinator> appMenuCoordinatorSupplier, Resources resources) {
mPropertyModel = propertyModel;
mShouldShowAppUpdateBadge = shouldShowAppUpdateBadge;
mIsActivityFinishingSupplier = isActivityFinishingSupplier;
mRequestRenderRunnable = requestRenderRunnable;
mThemeColorProvider = themeColorProvider;
mIsInOverviewModeSupplier = isInOverviewModeSupplier;
mControlsVisibilityDelegate = controlsVisibilityDelegate;
mSetUrlBarFocusFunction = setUrlBarFocusFunction;
mThemeColorProvider.addTintObserver(this::onTintChanged);
mAppMenuCoordinatorSupplierObserver = this::onAppMenuInitialized;
mAppMenuCoordinatorSupplier = appMenuCoordinatorSupplier;
mAppMenuCoordinatorSupplier.onAvailable(mAppMenuCoordinatorSupplierObserver);
mResources = resources;
mAppMenuButtonHelperSupplier = new ObservableSupplierImpl<>();
}
@Override
public void onMenuVisibilityChanged(boolean isVisible) {
if (isVisible) {
// Defocus here to avoid handling focus in multiple places, e.g., when the
// forward button is pressed. (see crbug.com/414219)
mSetUrlBarFocusFunction.setFocus(false, LocationBar.OmniboxFocusReason.UNFOCUS);
if (!mIsInOverviewModeSupplier.get() && isShowingAppMenuUpdateBadge()) {
// The app menu badge should be removed the first time the menu is opened.
removeAppMenuUpdateBadge(true);
mRequestRenderRunnable.run();
}
mFullscreenMenuToken =
mControlsVisibilityDelegate.showControlsPersistentAndClearOldToken(
mFullscreenMenuToken);
} else {
mControlsVisibilityDelegate.releasePersistentShowingToken(mFullscreenMenuToken);
}
if (isVisible) {
UpdateMenuItemHelper.getInstance().onMenuButtonClicked();
}
}
@Override
public void onMenuHighlightChanged(boolean isHighlighting) {
setMenuButtonHighlight(isHighlighting);
if (isHighlighting) {
mFullscreenHighlightToken =
mControlsVisibilityDelegate.showControlsPersistentAndClearOldToken(
mFullscreenHighlightToken);
} else {
mControlsVisibilityDelegate.releasePersistentShowingToken(mFullscreenHighlightToken);
}
}
void setClickable(boolean isClickable) {
mPropertyModel.set(MenuButtonProperties.IS_CLICKABLE, isClickable);
}
void onNativeInitialized() {
if (mShouldShowAppUpdateBadge) {
mUpdateStateChangedListener = this::updateStateChanged;
UpdateMenuItemHelper.getInstance().registerObserver(mUpdateStateChangedListener);
}
}
void setMenuButtonHighlight(boolean isHighlighting) {
mPropertyModel.set(MenuButtonProperties.IS_HIGHLIGHTING, isHighlighting);
}
void setAppMenuUpdateBadgeSuppressed(boolean isSuppressed) {
mSuppressAppMenuUpdateBadge = isSuppressed;
if (isSuppressed) {
removeAppMenuUpdateBadge(false);
} else if (isUpdateAvailable() && mShouldShowAppUpdateBadge) {
showAppMenuUpdateBadge(false);
}
}
void setVisibility(boolean visible) {
mPropertyModel.set(MenuButtonProperties.IS_VISIBLE, visible);
}
void updateReloadingState(boolean isLoading) {
if (mAppMenuPropertiesDelegate == null || mAppMenuHandler == null) {
return;
}
mAppMenuPropertiesDelegate.loadingStateChanged(isLoading);
mAppMenuHandler.menuItemContentChanged(org.chromium.chrome.R.id.icon_row_menu_id);
}
ObservableSupplier<AppMenuButtonHelper> getMenuButtonHelperSupplier() {
return mAppMenuButtonHelperSupplier;
}
void destroy() {
if (mAppMenuButtonHelper != null) {
mAppMenuHandler.removeObserver(this);
mAppMenuButtonHelper = null;
}
if (mUpdateStateChangedListener != null) {
UpdateMenuItemHelper.getInstance().unregisterObserver(mUpdateStateChangedListener);
mUpdateStateChangedListener = null;
}
}
void updateStateChanged() {
if (mIsActivityFinishingSupplier.get() || !mShouldShowAppUpdateBadge) {
return;
}
if (isUpdateAvailable()) {
showAppMenuUpdateBadge(true);
mRequestRenderRunnable.run();
} else if (isShowingAppMenuUpdateBadge()) {
removeAppMenuUpdateBadge(true);
}
}
private void showAppMenuUpdateBadge(boolean animate) {
if (mSuppressAppMenuUpdateBadge) {
return;
}
MenuButtonState buttonState = UpdateMenuItemHelper.getInstance().getUiState().buttonState;
assert buttonState != null : "No button state when trying to show the badge.";
updateContentDescription(true, buttonState.menuContentDescription);
mPropertyModel.set(
MenuButtonProperties.SHOW_UPDATE_BADGE, new ShowBadgeProperty(true, animate));
}
private void removeAppMenuUpdateBadge(boolean animate) {
mPropertyModel.set(
MenuButtonProperties.SHOW_UPDATE_BADGE, new ShowBadgeProperty(false, animate));
updateContentDescription(false, 0);
}
private void onTintChanged(ColorStateList tintList, boolean useLight) {
mPropertyModel.set(MenuButtonProperties.THEME, new ThemeProperty(tintList, useLight));
}
/**
* Sets the content description for the menu button.
* @param isUpdateBadgeVisible Whether the update menu badge is visible.
* @param badgeContentDescription Resource id of the string to show if the update badge is
* visible.
*/
private void updateContentDescription(
boolean isUpdateBadgeVisible, int badgeContentDescription) {
if (isUpdateBadgeVisible) {
mPropertyModel.set(MenuButtonProperties.CONTENT_DESCRIPTION,
mResources.getString(badgeContentDescription));
} else {
mPropertyModel.set(MenuButtonProperties.CONTENT_DESCRIPTION,
mResources.getString(
org.chromium.chrome.R.string.accessibility_toolbar_btn_menu));
}
}
/**
* Called when the app menu and related properties delegate are available.
*
* @param appMenuCoordinator The coordinator for interacting with the menu.
*/
private void onAppMenuInitialized(AppMenuCoordinator appMenuCoordinator) {
assert mAppMenuHandler == null;
AppMenuHandler appMenuHandler = appMenuCoordinator.getAppMenuHandler();
mAppMenuHandler = appMenuHandler;
mAppMenuHandler.addObserver(this);
mAppMenuButtonHelper = mAppMenuHandler.createAppMenuButtonHelper();
mAppMenuButtonHelper.setOnAppMenuShownListener(
() -> { RecordUserAction.record("MobileToolbarShowMenu"); });
mPropertyModel.set(MenuButtonProperties.APP_MENU_BUTTON_HELPER, mAppMenuButtonHelper);
mAppMenuButtonHelperSupplier.set(mAppMenuButtonHelper);
mAppMenuPropertiesDelegate = appMenuCoordinator.getAppMenuPropertiesDelegate();
}
/**
* @return Whether the badge is showing (either in the toolbar).
*/
private boolean isShowingAppMenuUpdateBadge() {
return mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge;
}
private boolean isUpdateAvailable() {
return UpdateMenuItemHelper.getInstance().getUiState().buttonState != null;
}
}
// Copyright 2020 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.menu_button;
import android.content.res.ColorStateList;
import androidx.annotation.NonNull;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableBooleanPropertyKey;
import org.chromium.ui.modelutil.PropertyModel.WritableObjectPropertyKey;
class MenuButtonProperties {
static class ThemeProperty {
@NonNull
public ColorStateList mColorStateList;
public boolean mUseLightColors;
public ThemeProperty(@NonNull ColorStateList colorStateList, boolean useLight) {
mColorStateList = colorStateList;
mUseLightColors = useLight;
}
}
static class ShowBadgeProperty {
public boolean mShowUpdateBadge;
public boolean mShouldAnimate;
public ShowBadgeProperty(boolean showUpdateBadge, boolean shouldAnimate) {
mShowUpdateBadge = showUpdateBadge;
mShouldAnimate = shouldAnimate;
}
}
public static final WritableObjectPropertyKey<AppMenuButtonHelper> APP_MENU_BUTTON_HELPER =
new WritableObjectPropertyKey<>();
public static final WritableObjectPropertyKey<String> CONTENT_DESCRIPTION =
new WritableObjectPropertyKey<>();
public static final WritableBooleanPropertyKey IS_CLICKABLE = new WritableBooleanPropertyKey();
public static final WritableBooleanPropertyKey IS_HIGHLIGHTING =
new WritableBooleanPropertyKey();
public static final WritableBooleanPropertyKey IS_VISIBLE = new WritableBooleanPropertyKey();
public static final WritableObjectPropertyKey<ShowBadgeProperty> SHOW_UPDATE_BADGE =
new WritableObjectPropertyKey(true);
public static final WritableObjectPropertyKey<ThemeProperty> THEME =
new WritableObjectPropertyKey<>(true);
public static final PropertyKey[] ALL_KEYS =
new PropertyKey[] {APP_MENU_BUTTON_HELPER, CONTENT_DESCRIPTION, IS_CLICKABLE,
IS_HIGHLIGHTING, IS_VISIBLE, SHOW_UPDATE_BADGE, THEME};
}
// Copyright 2020 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.menu_button;
import android.view.View;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ShowBadgeProperty;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ThemeProperty;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor.ViewBinder;
class MenuButtonViewBinder implements ViewBinder<PropertyModel, MenuButton, PropertyKey> {
@Override
public void bind(PropertyModel model, MenuButton view, PropertyKey propertyKey) {
if (propertyKey == MenuButtonProperties.APP_MENU_BUTTON_HELPER) {
view.setAppMenuButtonHelper(model.get(MenuButtonProperties.APP_MENU_BUTTON_HELPER));
} else if (propertyKey == MenuButtonProperties.CONTENT_DESCRIPTION) {
view.updateContentDescription(model.get(MenuButtonProperties.CONTENT_DESCRIPTION));
} else if (propertyKey == MenuButtonProperties.IS_CLICKABLE) {
view.setClickable(model.get(MenuButtonProperties.IS_CLICKABLE));
} else if (propertyKey == MenuButtonProperties.IS_HIGHLIGHTING) {
view.setMenuButtonHighlight(model.get(MenuButtonProperties.IS_HIGHLIGHTING));
} else if (propertyKey == MenuButtonProperties.IS_VISIBLE) {
view.setVisibility(
model.get(MenuButtonProperties.IS_VISIBLE) ? View.VISIBLE : View.GONE);
} else if (propertyKey == MenuButtonProperties.SHOW_UPDATE_BADGE) {
ShowBadgeProperty showBadgeProperty = model.get(MenuButtonProperties.SHOW_UPDATE_BADGE);
if (showBadgeProperty.mShowUpdateBadge) {
view.showAppMenuUpdateBadge(showBadgeProperty.mShouldAnimate);
} else {
view.removeAppMenuUpdateBadge(showBadgeProperty.mShouldAnimate);
}
} else if (propertyKey == MenuButtonProperties.THEME) {
ThemeProperty themeProperty = model.get(MenuButtonProperties.THEME);
view.onTintChanged(themeProperty.mColorStateList, themeProperty.mUseLightColors);
}
}
}
......@@ -227,8 +227,7 @@ public class StartSurfaceToolbarCoordinator {
mView = (StartSurfaceToolbarView) mStub.inflate();
mMenuButtonCoordinator.setMenuButton(mView.findViewById(R.id.menu_button_wrapper));
mMenuButtonCoordinator.setVisibility(
mPropertyModel.get(StartSurfaceToolbarProperties.MENU_IS_VISIBLE) ? View.VISIBLE
: View.GONE);
mPropertyModel.get(StartSurfaceToolbarProperties.MENU_IS_VISIBLE));
mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create(
mPropertyModel, mView, StartSurfaceToolbarViewBinder::bind);
if (LibraryLoader.getInstance().isInitialized()) {
......
......@@ -118,7 +118,6 @@ public class TabSwitcherModeTTPhone extends OptimizedFrameLayout
mIncognitoToggleTabLayout = null;
}
if (mMenuButton != null) {
mMenuButton.destroy();
mMenuButton = null;
}
}
......
......@@ -365,7 +365,7 @@ public class ToolbarPhone extends ToolbarLayout implements Invalidator.Client, O
setLayoutTransition(null);
if (getMenuButtonCoordinator() != null) {
getMenuButtonCoordinator().setVisibility(View.VISIBLE);
getMenuButtonCoordinator().setVisibility(true);
}
inflateTabSwitchingResources();
......@@ -1881,7 +1881,7 @@ public class ToolbarPhone extends ToolbarLayout implements Invalidator.Client, O
mToggleTabStackButton.setVisibility(isGone ? GONE : VISIBLE);
}
getMenuButtonCoordinator().setVisibility(inTabSwitcherMode ? GONE : VISIBLE);
getMenuButtonCoordinator().setVisibility(!inTabSwitcherMode);
triggerUrlFocusAnimation(inTabSwitcherMode && !urlHasFocus());
......@@ -2509,8 +2509,7 @@ public class ToolbarPhone extends ToolbarLayout implements Invalidator.Client, O
updateNtpTransitionAnimation();
}
getMenuButtonCoordinator().setMenuButtonHighlightDrawable();
getMenuButtonCoordinator().setVisibility(View.VISIBLE);
getMenuButtonCoordinator().setVisibility(true);
DrawableCompat.setTint(mLocationBarBackground,
isIncognito() ? Color.WHITE
......
......@@ -499,7 +499,7 @@ public class ToolbarTablet extends ToolbarLayout
void initialize(ToolbarDataProvider toolbarDataProvider, ToolbarTabController tabController,
MenuButtonCoordinator menuButtonCoordinator) {
super.initialize(toolbarDataProvider, tabController, menuButtonCoordinator);
menuButtonCoordinator.setVisibility(View.VISIBLE);
menuButtonCoordinator.setVisibility(true);
}
@Override
......
......@@ -4,13 +4,12 @@
package org.chromium.chrome.browser.toolbar.menu_button;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.widget.ImageButton;
import org.junit.Before;
import org.junit.Test;
......@@ -23,12 +22,10 @@ import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.ui.util.TokenHolder;
/**
* Unit tests for ToolbarAppMenuManager.
......@@ -50,6 +47,8 @@ public class MenuButtonCoordinatorTest {
@Mock
MenuButton mMenuButton;
@Mock
ImageButton mImageButton;
@Mock
private AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
@Mock
private UpdateMenuItemHelper mUpdateMenuItemHelper;
......@@ -77,6 +76,7 @@ public class MenuButtonCoordinatorTest {
doReturn(mMenuButton)
.when(mActivity)
.findViewById(org.chromium.chrome.R.id.menu_button_wrapper);
doReturn(mImageButton).when(mMenuButton).getImageButton();
mMenuButtonCoordinator = new MenuButtonCoordinator(mAppMenuSupplier,
mControlsVisibilityDelegate, mActivity, mFocusFunction, mRequestRenderRunnable,
......@@ -85,129 +85,14 @@ public class MenuButtonCoordinatorTest {
}
@Test
public void testInitialization() {
mAppMenuSupplier.set(mAppMenuCoordinator);
verify(mAppMenuHandler).addObserver(mMenuButtonCoordinator);
verify(mAppMenuHandler).createAppMenuButtonHelper();
}
@Test
public void testSetMenuButton() {
mMenuButtonCoordinator = new MenuButtonCoordinator(mAppMenuSupplier,
mControlsVisibilityDelegate, mActivity, mFocusFunction, mRequestRenderRunnable,
true, () -> false, mThemeColorProvider, org.chromium.chrome.R.id.none);
mAppMenuSupplier.set(mAppMenuCoordinator);
mMenuButtonCoordinator.setMenuButton(mMenuButton);
verify(mMenuButton, times(2)).setAppMenuButtonHelper(mAppMenuButtonHelper);
verify(mMenuButton, times(2)).setThemeColorProvider(mThemeColorProvider);
}
@Test
public void testAppMenuVisiblityChange_badgeShowing() {
mAppMenuSupplier.set(mAppMenuCoordinator);
doReturn(42)
.when(mControlsVisibilityDelegate)
.showControlsPersistentAndClearOldToken(TokenHolder.INVALID_TOKEN);
doReturn(true).when(mMenuButton).isShowingAppMenuUpdateBadge();
mMenuButtonCoordinator.onMenuVisibilityChanged(true);
verify(mFocusFunction).setFocus(false, LocationBar.OmniboxFocusReason.UNFOCUS);
verify(mMenuButton).removeAppMenuUpdateBadge(true);
verify(mUpdateMenuItemHelper).onMenuButtonClicked();
mMenuButtonCoordinator.onMenuVisibilityChanged(false);
verify(mControlsVisibilityDelegate).releasePersistentShowingToken(42);
}
@Test
public void testAppMenuHighlightChange() {
public void testEnterKeyPress() {
mAppMenuSupplier.set(mAppMenuCoordinator);
doReturn(42)
.when(mControlsVisibilityDelegate)
.showControlsPersistentAndClearOldToken(TokenHolder.INVALID_TOKEN);
mMenuButtonCoordinator.onMenuHighlightChanged(true);
verify(mMenuButton).setMenuButtonHighlight(true);
mMenuButtonCoordinator.onMenuHighlightChanged(false);
verify(mMenuButton).setMenuButtonHighlight(false);
verify(mControlsVisibilityDelegate).releasePersistentShowingToken(42);
}
mMenuButtonCoordinator.onEnterKeyPress();
verify(mAppMenuButtonHelper).onEnterKeyPress(mImageButton);
@Test
public void testAppMenuUpdateBadge() {
mAppMenuSupplier.set(mAppMenuCoordinator);
doReturn(true).when(mActivity).isDestroyed();
mMenuButtonCoordinator.updateStateChanged();
verify(mMenuButton, never()).showAppMenuUpdateBadgeIfAvailable(anyBoolean());
verify(mRequestRenderRunnable, never()).run();
verify(mMenuButton, never()).removeAppMenuUpdateBadge(false);
doReturn(false).when(mActivity).isDestroyed();
mMenuButtonCoordinator.updateStateChanged();
verify(mMenuButton, never()).showAppMenuUpdateBadgeIfAvailable(anyBoolean());
verify(mRequestRenderRunnable, never()).run();
verify(mMenuButton, times(1)).removeAppMenuUpdateBadge(false);
mMenuUiState.buttonState = new UpdateMenuItemHelper.MenuButtonState();
mMenuButtonCoordinator.updateStateChanged();
verify(mMenuButton).showAppMenuUpdateBadgeIfAvailable(true);
verify(mRequestRenderRunnable).run();
verify(mMenuButton, times(1)).removeAppMenuUpdateBadge(false);
}
@Test
public void testAppMenuUpdateBadge_activityShouldNotShow() {
MenuButtonCoordinator newCoordinator = new MenuButtonCoordinator(mAppMenuSupplier,
mControlsVisibilityDelegate, mActivity, mFocusFunction, mRequestRenderRunnable,
false,
() -> false, mThemeColorProvider, org.chromium.chrome.R.id.menu_button_wrapper);
doReturn(true).when(mActivity).isDestroyed();
newCoordinator.updateStateChanged();
verify(mMenuButton, never()).showAppMenuUpdateBadgeIfAvailable(anyBoolean());
verify(mRequestRenderRunnable, never()).run();
verify(mMenuButton, never()).removeAppMenuUpdateBadge(false);
doReturn(false).when(mActivity).isDestroyed();
newCoordinator.updateStateChanged();
verify(mMenuButton, never()).showAppMenuUpdateBadgeIfAvailable(anyBoolean());
verify(mRequestRenderRunnable, never()).run();
verify(mMenuButton, never()).removeAppMenuUpdateBadge(false);
mMenuUiState.buttonState = new UpdateMenuItemHelper.MenuButtonState();
newCoordinator.updateStateChanged();
verify(mMenuButton, never()).showAppMenuUpdateBadgeIfAvailable(anyBoolean());
verify(mRequestRenderRunnable, never()).run();
verify(mMenuButton, never()).removeAppMenuUpdateBadge(false);
}
@Test
public void testDestroyIsSafe() {
mMenuButtonCoordinator.destroy();
// It should be crash-safe to call public methods, but the results aren't meaningful.
mMenuButtonCoordinator.getMenuButtonHelperSupplier();
mMenuButtonCoordinator.onMenuHighlightChanged(true);
mMenuButtonCoordinator.onMenuVisibilityChanged(false);
mMenuButtonCoordinator.onNativeInitialized();
mMenuButtonCoordinator.setAppMenuUpdateBadgeSuppressed(true);
mMenuButtonCoordinator.updateReloadingState(true);
mMenuButtonCoordinator.updateStateChanged();
}
@Test
public void testDisableDestroysButton() {
mMenuButtonCoordinator.disableMenuButton();
verify(mMenuButton).destroy();
mMenuButtonCoordinator.onEnterKeyPress();
verify(mAppMenuButtonHelper, times(1)).onEnterKeyPress(mImageButton);
}
}
// Copyright 2020 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.menu_button;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.Activity;
import android.content.res.Resources;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.supplier.OneshotSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ShowBadgeProperty;
import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonProperties.ThemeProperty;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.util.TokenHolder;
/**
* Unit tests for ToolbarAppMenuManager.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class MenuButtonMediatorTest {
@Mock
private BrowserStateBrowserControlsVisibilityDelegate mControlsVisibilityDelegate;
@Mock
private Activity mActivity;
@Mock
private MenuButtonCoordinator.SetFocusFunction mFocusFunction;
@Mock
private AppMenuCoordinator mAppMenuCoordinator;
@Mock
private AppMenuHandler mAppMenuHandler;
@Mock
private AppMenuButtonHelper mAppMenuButtonHelper;
@Mock
private AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
@Mock
private UpdateMenuItemHelper mUpdateMenuItemHelper;
@Mock
private Runnable mRequestRenderRunnable;
@Mock
ThemeColorProvider mThemeColorProvider;
@Mock
Resources mResources;
private UpdateMenuItemHelper.MenuUiState mMenuUiState;
private OneshotSupplierImpl<AppMenuCoordinator> mAppMenuSupplier;
private PropertyModel mPropertyModel;
private MenuButtonMediator mMenuButtonMediator;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mPropertyModel = new PropertyModel.Builder(MenuButtonProperties.ALL_KEYS)
.with(MenuButtonProperties.SHOW_UPDATE_BADGE,
new ShowBadgeProperty(false, false))
.with(MenuButtonProperties.THEME,
new ThemeProperty(mThemeColorProvider.getTint(),
mThemeColorProvider.useLight()))
.with(MenuButtonProperties.IS_VISIBLE, true)
.build();
doReturn(mAppMenuHandler).when(mAppMenuCoordinator).getAppMenuHandler();
doReturn(mAppMenuButtonHelper).when(mAppMenuHandler).createAppMenuButtonHelper();
doReturn(mAppMenuPropertiesDelegate)
.when(mAppMenuCoordinator)
.getAppMenuPropertiesDelegate();
UpdateMenuItemHelper.setInstanceForTesting(mUpdateMenuItemHelper);
mAppMenuSupplier = new OneshotSupplierImpl<>();
mMenuUiState = new UpdateMenuItemHelper.MenuUiState();
doReturn(mMenuUiState).when(mUpdateMenuItemHelper).getUiState();
mMenuButtonMediator = new MenuButtonMediator(mPropertyModel, true,
()
-> false,
mRequestRenderRunnable, mThemeColorProvider,
()
-> false,
mControlsVisibilityDelegate, mFocusFunction, mAppMenuSupplier, mResources);
}
@Test
public void testInitialization() {
mAppMenuSupplier.set(mAppMenuCoordinator);
verify(mAppMenuHandler).addObserver(mMenuButtonMediator);
verify(mAppMenuHandler).createAppMenuButtonHelper();
}
@Test
public void testAppMenuVisiblityChange_badgeShowing() {
mAppMenuSupplier.set(mAppMenuCoordinator);
doReturn(42)
.when(mControlsVisibilityDelegate)
.showControlsPersistentAndClearOldToken(TokenHolder.INVALID_TOKEN);
mPropertyModel.set(
MenuButtonProperties.SHOW_UPDATE_BADGE, new ShowBadgeProperty(true, false));
mMenuButtonMediator.onMenuVisibilityChanged(true);
verify(mFocusFunction).setFocus(false, LocationBar.OmniboxFocusReason.UNFOCUS);
assertFalse(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mUpdateMenuItemHelper).onMenuButtonClicked();
mMenuButtonMediator.onMenuVisibilityChanged(false);
verify(mControlsVisibilityDelegate).releasePersistentShowingToken(42);
}
@Test
public void testAppMenuHighlightChange() {
mAppMenuSupplier.set(mAppMenuCoordinator);
doReturn(42)
.when(mControlsVisibilityDelegate)
.showControlsPersistentAndClearOldToken(TokenHolder.INVALID_TOKEN);
mMenuButtonMediator.onMenuHighlightChanged(true);
assertTrue(mPropertyModel.get(MenuButtonProperties.IS_HIGHLIGHTING));
mMenuButtonMediator.onMenuHighlightChanged(false);
assertFalse(mPropertyModel.get(MenuButtonProperties.IS_HIGHLIGHTING));
verify(mControlsVisibilityDelegate).releasePersistentShowingToken(42);
}
@Test
public void testAppMenuUpdateBadge() {
mAppMenuSupplier.set(mAppMenuCoordinator);
doReturn(true).when(mActivity).isDestroyed();
mMenuButtonMediator.updateStateChanged();
assertFalse(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mRequestRenderRunnable, never()).run();
doReturn(false).when(mActivity).isDestroyed();
mMenuButtonMediator.updateStateChanged();
assertFalse(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mRequestRenderRunnable, never()).run();
mMenuUiState.buttonState = new UpdateMenuItemHelper.MenuButtonState();
mMenuButtonMediator.updateStateChanged();
assertTrue(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mRequestRenderRunnable).run();
}
@Test
public void testAppMenuUpdateBadge_activityShouldNotShow() {
MenuButtonMediator newMediator = new MenuButtonMediator(mPropertyModel, false,
()
-> false,
mRequestRenderRunnable, mThemeColorProvider,
()
-> false,
mControlsVisibilityDelegate, mFocusFunction, mAppMenuSupplier, mResources);
doReturn(true).when(mActivity).isDestroyed();
newMediator.updateStateChanged();
assertFalse(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mRequestRenderRunnable, never()).run();
doReturn(false).when(mActivity).isDestroyed();
newMediator.updateStateChanged();
assertFalse(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mRequestRenderRunnable, never()).run();
mMenuUiState.buttonState = new UpdateMenuItemHelper.MenuButtonState();
newMediator.updateStateChanged();
assertFalse(mPropertyModel.get(MenuButtonProperties.SHOW_UPDATE_BADGE).mShowUpdateBadge);
verify(mRequestRenderRunnable, never()).run();
}
@Test
public void testDestroyIsSafe() {
mMenuButtonMediator.destroy();
// It should be crash-safe to call public methods, but the results aren't meaningful.
mMenuButtonMediator.getMenuButtonHelperSupplier();
mMenuButtonMediator.onMenuHighlightChanged(true);
mMenuButtonMediator.onMenuVisibilityChanged(false);
mMenuButtonMediator.onNativeInitialized();
mMenuButtonMediator.setAppMenuUpdateBadgeSuppressed(true);
mMenuButtonMediator.updateReloadingState(true);
mMenuButtonMediator.updateStateChanged();
}
}
......@@ -92,7 +92,7 @@ public class MenuButtonTest {
ShadowDrawable darkDrawable = shadowOf(ApiCompatibilityUtils.getDrawable(
mActivity.getResources(), mMenuUiState.buttonState.darkBadgeIcon));
mMenuButton.showAppMenuUpdateBadgeIfAvailable(false);
mMenuButton.showAppMenuUpdateBadge(false);
ShadowDrawable drawnDrawable = shadowOf(mMenuButton.getTabSwitcherAnimationDrawable());
assertEquals(drawnDrawable.getCreatedFromResId(), darkDrawable.getCreatedFromResId());
assertNotEquals(drawnDrawable.getCreatedFromResId(), lightDrawable.getCreatedFromResId());
......@@ -105,8 +105,7 @@ public class MenuButtonTest {
@Test
public void testDrawTabSwitcherAnimationOverlay_updateBadgeNotAvailable() {
mMenuUiState.buttonState = null;
mMenuButton.showAppMenuUpdateBadgeIfAvailable(false);
mMenuButton.removeAppMenuUpdateBadge(false);
Bitmap drawnBitmap =
((BitmapDrawable) mMenuButton.getTabSwitcherAnimationDrawable()).getBitmap();
......@@ -117,8 +116,7 @@ public class MenuButtonTest {
@Test
public void testDrawTabSwitcherAnimationOverlay_correctBoundsAfterThemeChange() {
mMenuUiState.buttonState = null;
mMenuButton.showAppMenuUpdateBadgeIfAvailable(false);
mMenuButton.removeAppMenuUpdateBadge(false);
mMenuButton.onTintChanged(mColorStateList, true);
// Run a manual layout pass so that mMenuButton's children get assigned sizes.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment