Commit 7d18e3fc authored by Theresa's avatar Theresa Committed by Commit Bot

Introduce ChromeSupplier for async dependencies

Introduce ChromeSupplier and use it to provide OverviewModeBehavior to
the app menu and SystemUiCoordinator/NavigationBarColorController.

The ChromeSupplier allows classes to register a callback to be notified
when the wrapped dependency is available.

BUG=956260, 960129

Change-Id: If1e363be247a237b45931b1727cb76f2a6ee694b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1610205
Commit-Queue: Theresa <twellington@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Cr-Commit-Position: refs/heads/master@{#661013}
parent 988b56b5
......@@ -1629,6 +1629,8 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/util/IntentUtils.java",
"java/src/org/chromium/chrome/browser/util/KeyNavigationUtil.java",
"java/src/org/chromium/chrome/browser/util/MathUtils.java",
"java/src/org/chromium/chrome/browser/util/ObservableSupplier.java",
"java/src/org/chromium/chrome/browser/util/ObservableSupplierImpl.java",
"java/src/org/chromium/chrome/browser/util/PlatformUtil.java",
"java/src/org/chromium/chrome/browser/util/UrlUtilities.java",
"java/src/org/chromium/chrome/browser/util/ViewUtils.java",
......
......@@ -181,6 +181,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/toolbar/ToolbarSecurityIconTest.java",
"junit/src/org/chromium/chrome/browser/usage_stats/EventTrackerTest.java",
"junit/src/org/chromium/chrome/browser/usage_stats/PageViewObserverTest.java",
"junit/src/org/chromium/chrome/browser/util/ObservableSupplierImplTest.java",
"junit/src/org/chromium/chrome/browser/util/UrlUtilitiesUnitTest.java",
"junit/src/org/chromium/chrome/browser/util/test/ShadowUrlUtilities.java",
"junit/src/org/chromium/chrome/browser/webapps/MockWebappDataStorageClockRule.java",
......
......@@ -148,6 +148,7 @@ import org.chromium.chrome.browser.ui.system.StatusBarColorController;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.chrome.browser.vr.ArDelegate;
import org.chromium.chrome.browser.vr.ArDelegateProvider;
import org.chromium.chrome.browser.vr.VrModuleProvider;
......@@ -763,7 +764,7 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
public AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
return new AppMenuPropertiesDelegate(this, getActivityTabProvider(),
getMultiWindowModeStateDispatcher(), getTabModelSelector(), getToolbarManager(),
getWindow().getDecorView());
getWindow().getDecorView(), null);
}
/**
......@@ -1442,6 +1443,14 @@ public abstract class ChromeActivity<C extends ChromeActivityComponent>
return null;
}
/**
* @return {@link ObservableSupplier} for the {@link OverviewModeBehavior} for this activity
* if it supports an overview mode, null otherwise.
*/
public @Nullable ObservableSupplier<OverviewModeBehavior> getOverviewModeBehaviorSupplier() {
return null;
}
/**
* @return Whether this Activity should initialize the BottomSheet and BottomSheetController.
*/
......
......@@ -149,6 +149,8 @@ import org.chromium.chrome.browser.usage_stats.UsageStatsService;
import org.chromium.chrome.browser.util.AccessibilityUtil;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.chrome.browser.util.ObservableSupplierImpl;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.chrome.browser.widget.OverviewListLayout;
import org.chromium.components.feature_engagement.EventConstants;
......@@ -332,6 +334,8 @@ public class ChromeTabbedActivity
implements OverviewModeObserver, OverviewModeController {
private OverviewModeController mInternalOverviewModeController;
private ObserverList<OverviewModeObserver> mOverviewModeObserverList = new ObserverList<>();
private final ObservableSupplierImpl<OverviewModeBehavior> mOverviewModeBehaviorSupplier =
new ObservableSupplierImpl<>();
@Override
public boolean overviewVisible() {
......@@ -392,15 +396,17 @@ public class ChromeTabbedActivity
}
/**
* Provide a new internal implementation to use for {@link OverviewModeController}.
* @param newOverviewModeController The new internal implementation to use.
* Provide an internal implementation to use for {@link OverviewModeController}.
* @param overviewModeController The internal implementation to use.
*/
void overrideOverviewModeController(OverviewModeController newOverviewModeController) {
void overrideOverviewModeController(OverviewModeController overviewModeController) {
if (mInternalOverviewModeController != null) {
mInternalOverviewModeController.removeOverviewModeObserver(this);
}
mInternalOverviewModeController = newOverviewModeController;
mInternalOverviewModeController = overviewModeController;
mInternalOverviewModeController.addOverviewModeObserver(this);
mOverviewModeBehaviorSupplier.set(overviewModeController);
}
}
......@@ -756,7 +762,10 @@ public class ChromeTabbedActivity
TabManagementModuleProvider.getDelegate().createGridTabSwitcher(this);
mOverviewModeController.overrideOverviewModeController(
gridTabSwitcher.getOverviewModeController());
} else {
mOverviewModeController.overrideOverviewModeController(mLayoutManager);
}
mOverviewModeController.addOverviewModeObserver(this);
if (ChromeFeatureList.isEnabled(ChromeFeatureList.TAB_ENGAGEMENT_REPORTING_ANDROID)) {
// The lifecycle of this object is managed by the lifecycle dispatcher.
......@@ -916,6 +925,11 @@ public class ChromeTabbedActivity
return mOverviewModeController;
}
@Override
public @Nullable ObservableSupplier<OverviewModeBehavior> getOverviewModeBehaviorSupplier() {
return mOverviewModeController.mOverviewModeBehaviorSupplier;
}
@Override
protected AssistStatusHandler createAssistStatusHandler() {
return new TabbedAssistStatusHandler(this);
......@@ -963,8 +977,6 @@ public class ChromeTabbedActivity
new LayoutManagerChromePhone(compositorViewHolder, mOverviewModeController);
}
mLayoutManager.setEnableAnimations(DeviceClassManager.enableAnimations());
mOverviewModeController.overrideOverviewModeController(mLayoutManager);
mOverviewModeController.addOverviewModeObserver(this);
// TODO(yusufo): get rid of findViewById(R.id.url_bar).
initializeCompositorContent(mLayoutManager, findViewById(R.id.url_bar),
......@@ -1649,7 +1661,8 @@ public class ChromeTabbedActivity
public AppMenuPropertiesDelegate createAppMenuPropertiesDelegate() {
return new TabbedAppMenuPropertiesDelegate(this, getActivityTabProvider(),
getMultiWindowModeStateDispatcher(), getTabModelSelector(), getToolbarManager(),
getWindow().getDecorView(), this);
getWindow().getDecorView(), this,
mOverviewModeController.mOverviewModeBehaviorSupplier);
}
@Override
......
......@@ -17,6 +17,7 @@ import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.util.ObservableSupplier;
/** A UI coordinator the app menu. */
public class AppMenuCoordinator {
......@@ -69,11 +70,14 @@ public class AppMenuCoordinator {
* It is assumed to have back_menu_id, forward_menu_id, bookmark_this_page_id.
* @param decorView The decor {@link View}, e.g. from Window#getDecorView(), for the
* containing activity.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
* @return AppMenuHandler for the given activity and menu resource id.
*/
AppMenuHandler get(AppMenuPropertiesDelegate delegate, AppMenuDelegate appMenuDelegate,
int menuResourceId, View decorView,
ActivityLifecycleDispatcher activityLifecycleDispatcher);
ActivityLifecycleDispatcher activityLifecycleDispatcher,
@Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier);
}
/**
......@@ -91,10 +95,10 @@ public class AppMenuCoordinator {
private AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
private AppMenuHandler mAppMenuHandler;
private static AppMenuHandlerFactory sAppMenuHandlerFactory =
(delegate, appMenuDelegate, menuResourceId, decorView, activityLifecycleDispatcher)
private static AppMenuHandlerFactory sAppMenuHandlerFactory = (delegate, appMenuDelegate,
menuResourceId, decorView, activityLifecycleDispatcher, overviewModeBehaviorSupplier)
-> new AppMenuHandler(delegate, appMenuDelegate, menuResourceId, decorView,
activityLifecycleDispatcher);
activityLifecycleDispatcher, overviewModeBehaviorSupplier);
/**
* Construct a new AppMenuCoordinator.
......@@ -105,10 +109,13 @@ public class AppMenuCoordinator {
* @param appMenuDelegate The {@link AppMenuDelegate} for the containing activity.
* @param decorView The decor {@link View}, e.g. from Window#getDecorView(), for the containing
* activity.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
*/
public AppMenuCoordinator(Context context,
ActivityLifecycleDispatcher activityLifecycleDispatcher,
MenuButtonDelegate buttonDelegate, AppMenuDelegate appMenuDelegate, View decorView) {
MenuButtonDelegate buttonDelegate, AppMenuDelegate appMenuDelegate, View decorView,
@Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
mContext = context;
mButtonDelegate = buttonDelegate;
mAppMenuDelegate = appMenuDelegate;
......@@ -116,7 +123,7 @@ public class AppMenuCoordinator {
mAppMenuHandler = sAppMenuHandlerFactory.get(mAppMenuPropertiesDelegate, mAppMenuDelegate,
mAppMenuPropertiesDelegate.getAppMenuLayoutId(), decorView,
activityLifecycleDispatcher);
activityLifecycleDispatcher, overviewModeBehaviorSupplier);
// TODO(twellington): Move to UpdateMenuItemHelper or common UI coordinator parent?
mAppMenuHandler.addObserver(new AppMenuObserver() {
......@@ -143,20 +150,8 @@ public class AppMenuCoordinator {
public void destroy() {
// Prevent the menu window from leaking.
if (mAppMenuHandler != null) mAppMenuHandler.destroy();
}
/**
* Called when native initialization has finished to provide additional activity-scoped objects
* only available after native initialization.
*
* @param overviewModeBehavior The {@link OverviewModeBehavior} for the containing activity
* if the current activity supports an overview mode, or null otherwise.
*/
public void onNativeInitialized(@Nullable OverviewModeBehavior overviewModeBehavior) {
// TODO(https://crbug.com/956260): Look into providing this during construction
// (e.g. through an AsyncSupplier that can notify when the dependency is available).
mAppMenuPropertiesDelegate.onNativeInitialized(overviewModeBehavior);
mAppMenuHandler.onNativeInitialized(overviewModeBehavior);
mAppMenuPropertiesDelegate.destroy();
}
/**
......
......@@ -19,6 +19,7 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.PopupMenu;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
......@@ -26,6 +27,7 @@ import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.ConfigurationChangedObserver;
import org.chromium.chrome.browser.lifecycle.StartStopWithNativeObserver;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import java.util.ArrayList;
......@@ -47,7 +49,9 @@ public class AppMenuHandler implements StartStopWithNativeObserver, Configuratio
private final AppMenuCoordinator.AppMenuDelegate mAppMenuDelegate;
private final View mDecorView;
private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
private OverviewModeBehavior mOverviewModeBehavior;
private final @Nullable ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
private @Nullable Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
private @Nullable OverviewModeBehavior mOverviewModeBehavior;
/**
* The resource id of the menu item to highlight when the menu next opens. A value of
......@@ -71,10 +75,13 @@ public class AppMenuHandler implements StartStopWithNativeObserver, Configuratio
* activity.
* @param activityLifecycleDispatcher The {@link ActivityLifecycleDispatcher} for the containing
* activity.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
*/
public AppMenuHandler(AppMenuPropertiesDelegate delegate,
AppMenuCoordinator.AppMenuDelegate appMenuDelegate, int menuResourceId, View decorView,
ActivityLifecycleDispatcher activityLifecycleDispatcher) {
ActivityLifecycleDispatcher activityLifecycleDispatcher,
@Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
mAppMenuDelegate = appMenuDelegate;
mDelegate = delegate;
mDecorView = decorView;
......@@ -85,6 +92,19 @@ public class AppMenuHandler implements StartStopWithNativeObserver, Configuratio
mActivityLifecycleDispatcher = activityLifecycleDispatcher;
mActivityLifecycleDispatcher.register(this);
mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
if (mOverviewModeBehavior != null) {
mOverviewModeSupplierCallback = overviewModeBehavior -> {
if (mOverviewModeBehavior != null) {
mOverviewModeBehavior.removeOverviewModeObserver(this);
}
mOverviewModeBehavior = overviewModeBehavior;
mOverviewModeBehavior.addOverviewModeObserver(this);
};
mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
}
assert mHardwareButtonMenuAnchor != null
: "Using AppMenu requires to have menu_anchor_stub view";
}
......@@ -97,26 +117,15 @@ public class AppMenuHandler implements StartStopWithNativeObserver, Configuratio
hideAppMenu();
mActivityLifecycleDispatcher.unregister(this);
if (mOverviewModeBehaviorSupplier != null) {
mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
}
if (mOverviewModeBehavior != null) {
mOverviewModeBehavior.removeOverviewModeObserver(this);
}
}
/**
* Called when native initialization has finished to provide additional activity-scoped objects
* only available after native initialization.
*
* @param overviewModeBehavior The {@link OverviewModeBehavior} for the containing activity
* if the current activity supports an overview mode, or null otherwise.
*/
void onNativeInitialized(@Nullable OverviewModeBehavior overviewModeBehavior) {
if (overviewModeBehavior != null) {
mOverviewModeBehavior = overviewModeBehavior;
mOverviewModeBehavior.addOverviewModeObserver(this);
}
}
/**
* Notifies the menu that the contents of the menu item specified by {@code menuRowId} have
* changed. This should be called if icons, titles, etc. are changing for a particular menu
......
......@@ -18,6 +18,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import org.chromium.base.Callback;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.metrics.RecordHistogram;
......@@ -40,6 +41,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.translate.TranslateBridge;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.webapk.lib.client.WebApkValidator;
......@@ -57,6 +59,8 @@ public class AppMenuPropertiesDelegate {
protected final TabModelSelector mTabModelSelector;
protected final ToolbarManager mToolbarManager;
protected final View mDecorView;
private final @Nullable ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
private @Nullable Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
protected @Nullable OverviewModeBehavior mOverviewModeBehavior;
protected BookmarkBridge mBookmarkBridge;
......@@ -71,10 +75,13 @@ public class AppMenuPropertiesDelegate {
* @param toolbarManager The {@link ToolbarManager} for the containing activity.
* @param decorView The decor {@link View}, e.g. from Window#getDecorView(), for the containing
* activity.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
*/
public AppMenuPropertiesDelegate(Context context, ActivityTabProvider activityTabProvider,
MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView) {
TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView,
@Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
mContext = context;
mIsTablet = DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext);
mActivityTabProvider = activityTabProvider;
......@@ -82,17 +89,23 @@ public class AppMenuPropertiesDelegate {
mTabModelSelector = tabModelSelector;
mToolbarManager = toolbarManager;
mDecorView = decorView;
mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
if (mOverviewModeBehaviorSupplier != null) {
mOverviewModeSupplierCallback = overviewModeBehavior -> {
mOverviewModeBehavior = overviewModeBehavior;
};
mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
}
}
/**
* Called when native initialization has finished to provide additional activity-scoped objects
* only available after native initialization.
*
* @param overviewModeBehavior The {@link OverviewModeBehavior} for the containing activity
* if the current activity supports an overview mode, or null otherwise.
* Called when the containing activity is being destroyed.
*/
void onNativeInitialized(@Nullable OverviewModeBehavior overviewModeBehavior) {
mOverviewModeBehavior = overviewModeBehavior;
public void destroy() {
if (mOverviewModeBehaviorSupplier != null) {
mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
}
}
/**
......
......@@ -12,7 +12,7 @@ public interface OverviewModeBehavior {
/**
* An observer that is notified when the overview mode state changes.
*/
public interface OverviewModeObserver {
interface OverviewModeObserver {
/**
* Called when overview mode starts showing.
* @param showToolbar Whether or not to show the normal toolbar when animating into overview
......
......@@ -63,7 +63,7 @@ public class CustomTabAppMenuPropertiesDelegate extends AppMenuPropertiesDelegat
@CustomTabsUiType final int uiType, List<String> menuEntries, boolean isOpenedByChrome,
boolean showShare, boolean showStar, boolean showDownload, boolean isIncognito) {
super(context, activityTabProvider, multiWindowModeStateDispatcher, tabModelSelector,
toolbarManager, decorView);
toolbarManager, decorView, null);
mUiType = uiType;
mMenuEntries = menuEntries;
mIsOpenedByChrome = isOpenedByChrome;
......
......@@ -13,11 +13,13 @@ import org.chromium.chrome.browser.appmenu.AppMenu;
import org.chromium.chrome.browser.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.appmenu.AppMenuIconRowFooter;
import org.chromium.chrome.browser.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.datareduction.DataReductionMainMenuItem;
import org.chromium.chrome.browser.multiwindow.MultiWindowModeStateDispatcher;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.util.ObservableSupplier;
/**
* An {@link AppMenuPropertiesDelegate} for ChromeTabbedActivity.
......@@ -28,9 +30,10 @@ public class TabbedAppMenuPropertiesDelegate extends AppMenuPropertiesDelegate {
public TabbedAppMenuPropertiesDelegate(Context context, ActivityTabProvider activityTabProvider,
MultiWindowModeStateDispatcher multiWindowModeStateDispatcher,
TabModelSelector tabModelSelector, ToolbarManager toolbarManager, View decorView,
AppMenuCoordinator.AppMenuDelegate appMenuDelegate) {
AppMenuCoordinator.AppMenuDelegate appMenuDelegate,
ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
super(context, activityTabProvider, multiWindowModeStateDispatcher, tabModelSelector,
toolbarManager, decorView);
toolbarManager, decorView, overviewModeBehaviorSupplier);
mAppMenuDelegate = appMenuDelegate;
}
......
......@@ -32,6 +32,7 @@ public class RootUiCoordinator implements Destroyable, NativeInitObserver, Infla
private @Nullable AppMenuCoordinator mAppMenuCoordinator;
private @Nullable ImmersiveModeManager mImmersiveModeManager;
private SystemUiCoordinator mSystemUiCoordinator;
private @Nullable EmptyBackgroundViewWrapper mEmptyBackgroundViewWrapper;
/**
* Create a new {@link RootUiCoordinator} for the given activity.
......@@ -51,6 +52,7 @@ public class RootUiCoordinator implements Destroyable, NativeInitObserver, Infla
if (mSystemUiCoordinator != null) mSystemUiCoordinator.destroy();
if (mImmersiveModeManager != null) mImmersiveModeManager.destroy();
if (mAppMenuCoordinator != null) mAppMenuCoordinator.destroy();
if (mEmptyBackgroundViewWrapper != null) mEmptyBackgroundViewWrapper.destroy();
}
@Override
......@@ -63,7 +65,8 @@ public class RootUiCoordinator implements Destroyable, NativeInitObserver, Infla
if (mActivity.supportsAppMenu()) {
mAppMenuCoordinator = new AppMenuCoordinator(mActivity,
mActivity.getLifecycleDispatcher(), mActivity.getToolbarManager(), mActivity,
mActivity.getWindow().getDecorView());
mActivity.getWindow().getDecorView(),
mActivity.getOverviewModeBehaviorSupplier());
mActivity.getToolbarManager().onAppMenuInitialized(
mAppMenuCoordinator.getAppMenuHandler(),
mAppMenuCoordinator.getAppMenuPropertiesDelegate());
......@@ -73,9 +76,9 @@ public class RootUiCoordinator implements Destroyable, NativeInitObserver, Infla
mImmersiveModeManager = AppHooks.get().createImmersiveModeManager(
mActivity.getWindow().getDecorView().findViewById(android.R.id.content));
mSystemUiCoordinator =
new SystemUiCoordinator(mActivity.getWindow(), mActivity.getTabModelSelector(),
mImmersiveModeManager, mActivity.getActivityType());
mSystemUiCoordinator = new SystemUiCoordinator(mActivity.getWindow(),
mActivity.getTabModelSelector(), mImmersiveModeManager, mActivity.getActivityType(),
mActivity.getOverviewModeBehaviorSupplier());
if (mImmersiveModeManager != null && mActivity.getToolbarManager() != null) {
mActivity.getToolbarManager().setImmersiveModeManager(mImmersiveModeManager);
......@@ -84,21 +87,17 @@ public class RootUiCoordinator implements Destroyable, NativeInitObserver, Infla
@Override
public void onFinishNativeInitialization() {
mSystemUiCoordinator.onNativeInitialized(mActivity.getOverviewModeBehavior());
if (mAppMenuCoordinator != null) {
mAppMenuCoordinator.onNativeInitialized(mActivity.getOverviewModeBehavior());
}
// TODO(twellington): Move to a TabbedRootUiCoordinator or delegate?
// TODO(https://crbug.com/931496): Move to a TabbedRootUiCoordinator or delegate?
// TODO(twellington): Supply TabModelSelector as well and move initialization earlier.
if (mActivity.getActivityType() == ChromeActivity.ActivityType.TABBED
&& DeviceFormFactor.isNonMultiDisplayContextOnTablet(mActivity)) {
AppMenuHandler appMenuHandler =
mAppMenuCoordinator == null ? null : mAppMenuCoordinator.getAppMenuHandler();
EmptyBackgroundViewWrapper bgViewWrapper =
new EmptyBackgroundViewWrapper(mActivity.getTabModelSelector(),
mActivity.getTabCreator(false), mActivity, appMenuHandler,
mActivity.getSnackbarManager(), mActivity.getOverviewModeBehavior());
bgViewWrapper.initialize();
mEmptyBackgroundViewWrapper = new EmptyBackgroundViewWrapper(
mActivity.getTabModelSelector(), mActivity.getTabCreator(false), mActivity,
appMenuHandler, mActivity.getSnackbarManager(),
mActivity.getOverviewModeBehaviorSupplier());
mEmptyBackgroundViewWrapper.initialize();
}
}
......
......@@ -16,6 +16,7 @@ import android.view.Window;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.BuildInfo;
import org.chromium.base.Callback;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver;
......@@ -27,6 +28,7 @@ import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.ui.ImmersiveModeManager;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.chrome.browser.vr.VrModeObserver;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.ui.UiUtils;
......@@ -43,10 +45,11 @@ class NavigationBarColorController implements VrModeObserver {
// May be null if we return from the constructor early. Otherwise will be set.
private final @Nullable TabModelSelector mTabModelSelector;
private final @Nullable TabModelSelectorObserver mTabModelSelectorObserver;
private @Nullable ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
private @Nullable Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
private @Nullable OverviewModeBehavior mOverviewModeBehavior;
private @Nullable OverviewModeObserver mOverviewModeObserver;
private boolean mInitialized;
private boolean mUseLightNavigation;
private boolean mOverviewModeHiding;
......@@ -56,9 +59,12 @@ class NavigationBarColorController implements VrModeObserver {
* @param tabModelSelector The {@link TabModelSelector} used to determine which tab model is
* selected.
* @param immersiveModeManager The {@link ImmersiveModeManager} for the containing activity.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
*/
NavigationBarColorController(Window window, TabModelSelector tabModelSelector,
@Nullable ImmersiveModeManager immersiveModeManager) {
@Nullable ImmersiveModeManager immersiveModeManager,
ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
mWindow = window;
......@@ -89,7 +95,6 @@ class NavigationBarColorController implements VrModeObserver {
return;
}
mInitialized = true;
mUseLightNavigation = true;
mTabModelSelector = tabModelSelector;
......@@ -101,6 +106,10 @@ class NavigationBarColorController implements VrModeObserver {
};
mTabModelSelector.addObserver(mTabModelSelectorObserver);
mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
mOverviewModeSupplierCallback = this::setOverviewModeBehavior;
mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
// TODO(https://crbug.com/806054): Observe tab loads to restrict black bottom nav to
// incognito NTP.
......@@ -114,6 +123,9 @@ class NavigationBarColorController implements VrModeObserver {
*/
public void destroy() {
if (mTabModelSelector != null) mTabModelSelector.removeObserver(mTabModelSelectorObserver);
if (mOverviewModeBehaviorSupplier != null) {
mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
}
if (mOverviewModeBehavior != null) {
mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
}
......@@ -124,8 +136,10 @@ class NavigationBarColorController implements VrModeObserver {
* @param overviewModeBehavior The {@link OverviewModeBehavior} used to determine whether
* overview mode is showing.
*/
public void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
if (!mInitialized) return;
private void setOverviewModeBehavior(OverviewModeBehavior overviewModeBehavior) {
if (mOverviewModeBehavior != null) {
mOverviewModeBehavior.removeOverviewModeObserver(mOverviewModeObserver);
}
mOverviewModeBehavior = overviewModeBehavior;
mOverviewModeObserver = new EmptyOverviewModeObserver() {
......
......@@ -12,6 +12,7 @@ import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.ui.ImmersiveModeManager;
import org.chromium.chrome.browser.util.ObservableSupplier;
/**
* A UI coordinator that manages the system status bar and bottom navigation bar.
......@@ -29,30 +30,20 @@ public class SystemUiCoordinator {
* @param tabModelSelector The {@link TabModelSelector} for the containing activity.
* @param immersiveModeManager The {@link ImmersiveModeManager} for the containing activity.
* @param activityType The {@link org.chromium.chrome.browser.ChromeActivity.ActivityType} of
* the containing activity
* the containing activity.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
*/
public SystemUiCoordinator(Window window, TabModelSelector tabModelSelector,
@Nullable ImmersiveModeManager immersiveModeManager,
@ChromeActivity.ActivityType int activityType) {
@ChromeActivity.ActivityType int activityType,
@Nullable ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
// TODO(https://crbug.com/931496): Move to a TabbedSystemUiCoordinator or delegate?
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
&& activityType == ChromeActivity.ActivityType.TABBED) {
assert overviewModeBehaviorSupplier != null;
mNavigationBarColorController = new NavigationBarColorController(
window, tabModelSelector, immersiveModeManager);
}
}
/**
* Called when native initialization has finished to provide additional activity-scoped objects
* only available after native initialization.
*
* @param overviewModeBehavior The {@link OverviewModeBehavior} for the containing activity
* if the current activity supports an overview mode, or null otherwise.
*/
public void onNativeInitialized(@Nullable OverviewModeBehavior overviewModeBehavior) {
if (mNavigationBarColorController != null) {
assert overviewModeBehavior != null;
mNavigationBarColorController.setOverviewModeBehavior(overviewModeBehavior);
window, tabModelSelector, immersiveModeManager, overviewModeBehaviorSupplier);
}
}
......
// Copyright 2019 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.util;
import org.chromium.base.Callback;
import org.chromium.base.Supplier;
/**
* ObservableSupplier wraps an asynchronously provided object E, notifying observers when the
* dependency is available. This allows classes dependent on E to be provided with a
* ObservableSupplier during construction and register a Callback<E> to be notified when the needed
* dependency is available.
*
* This class must only be accessed from a single thread.
*
* For classes owning the ObservableSupplier and providing it as a dependency to others, see
* {@link ObservableSupplierImpl}.
*
* For classes using a ObservableSupplier to receive a dependency:
* - To be notified when the object is available, call {@link #addObserver(Callback)} with a
* Callback to be notified when the object is available.
* - If the object is already available, the Callback will be called immediately.
* - The Callback may be called multiple times if the object wrapped by the ObservableSupplier
* changes.
*
* @param <E> The type of the wrapped object.
*/
public interface ObservableSupplier<E> extends Supplier<E> {
/**
* @param obs An observer to be notified when the object owned by this supplier is available.
* If the object is already available, the callback will be notified at the end of the
* current message loop (so long as the object hasn't changed).
* @return The current object or null if it hasn't been set yet.
*/
E addObserver(Callback<E> obs);
/**
* @param obs The observer to remove.
*/
void removeObserver(Callback<E> obs);
}
\ No newline at end of file
// Copyright 2019 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.util;
import android.os.Handler;
import android.support.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.ObserverList;
/**
* Concrete implementation of {@link ObservableSupplier} to be used by classes owning the
* ObservableSupplier and providing it as a dependency to others.
*
* This class must only be accessed from a single thread.
*
* To use:
* 1. Create a new ObservableSupplierImpl<E> to pass as a dependency
* 2. Call {@link #set(Object)} when the real object becomes available. {@link #set(Object)} may
* be called multiple times. Observers will be notified each time a new object is set.
*
* @param <E> The type of the wrapped object.
*/
public class ObservableSupplierImpl<E> implements ObservableSupplier<E> {
private final Thread mThread = Thread.currentThread();
private final Handler mHandler = new Handler();
private E mObject;
private final ObserverList<Callback<E>> mObservers = new ObserverList<>();
@Override
public E addObserver(Callback<E> obs) {
checkThread();
mObservers.addObserver(obs);
if (mObject != null) {
final E currentObject = mObject;
mHandler.post(() -> {
if (mObject != currentObject || !mObservers.hasObserver(obs)) return;
obs.onResult(mObject);
});
}
return mObject;
}
@Override
public void removeObserver(Callback<E> obs) {
checkThread();
mObservers.removeObserver(obs);
}
/**
* Set the object supplied by this supplier. This will notify registered callbacks that the
* dependency is available.
* @param object The object to supply.
*/
public void set(E object) {
checkThread();
if (object == mObject) return;
mObject = object;
for (Callback<E> observer : mObservers) {
observer.onResult(mObject);
}
}
@Override
public @Nullable E get() {
checkThread();
return mObject;
}
private void checkThread() {
assert mThread == Thread.currentThread()
: "ObservableSupplierImpl must only be used on a single Thread.";
}
}
......@@ -10,6 +10,7 @@ import android.view.View;
import android.view.View.OnAttachStateChangeListener;
import android.view.ViewStub;
import org.chromium.base.Callback;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
......@@ -23,6 +24,7 @@ import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelObserver;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.util.ObservableSupplier;
import java.util.List;
......@@ -35,34 +37,42 @@ public class EmptyBackgroundViewWrapper {
private final TabCreator mTabCreator;
private final TabModelObserver mTabModelObserver;
private final TabModelSelectorObserver mTabModelSelectorObserver;
private final OverviewModeBehavior mOverviewModeBehavior;
private final SnackbarManager mSnackbarManager;
private final ObservableSupplier<OverviewModeBehavior> mOverviewModeBehaviorSupplier;
private final Callback<OverviewModeBehavior> mOverviewModeSupplierCallback;
private @Nullable OverviewModeBehavior mOverviewModeBehavior;
private EmptyBackgroundViewTablet mBackgroundView;
private final @Nullable AppMenuHandler mMenuHandler;
/**
* Creates a {@link EmptyBackgroundViewWrapper} instance that will lazily inflate.
* @param selector A {@link TabModelSelector} that will be used to query system
* state.
* @param tabCreator A {@link TabCreator} that will be used to open the New Tab Page.
* @param activity An {@link Activity} that represents a parent of the
* {@link android.view.ViewStub}.
* @param menuHandler A {@link AppMenuHandler} to handle menu touch events.
* @param snackbarManager The {@link SnackbarManager} to show the undo snackbar when the
* empty background is visible.
* @param overviewModeBehavior A {@link OverviewModeBehavior} instance to detect when the app
* is in overview mode.
* @param selector A {@link TabModelSelector} that will be used to query system state.
* @param tabCreator A {@link TabCreator} that will be used to open the New Tab Page.
* @param activity An {@link Activity} that represents a parent of th
* {@link android.view.ViewStub}.
* @param menuHandler A {@link AppMenuHandler} to handle menu touch events.
* @param snackbarManager The {@link SnackbarManager} to show the undo snackbar when the empty
* background is visible.
* @param overviewModeBehaviorSupplier An {@link ObservableSupplier} for the
* {@link OverviewModeBehavior} associated with the containing activity.
*/
public EmptyBackgroundViewWrapper(TabModelSelector selector, TabCreator tabCreator,
Activity activity, @Nullable AppMenuHandler menuHandler,
SnackbarManager snackbarManager, OverviewModeBehavior overviewModeBehavior) {
SnackbarManager snackbarManager,
ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
mActivity = activity;
mMenuHandler = menuHandler;
mTabModelSelector = selector;
mTabCreator = tabCreator;
mSnackbarManager = snackbarManager;
mOverviewModeBehavior = overviewModeBehavior;
mOverviewModeBehaviorSupplier = overviewModeBehaviorSupplier;
mOverviewModeSupplierCallback =
overviewModeBehavior -> mOverviewModeBehavior = overviewModeBehavior;
mOverviewModeBehaviorSupplier.addObserver(mOverviewModeSupplierCallback);
mTabModelObserver = new EmptyTabModelObserver() {
@Override
public void didAddTab(Tab tab, @TabLaunchType int type) {
......@@ -102,6 +112,13 @@ public class EmptyBackgroundViewWrapper {
};
}
/**
* Called when the containing activity is being destroyed.
*/
public void destroy() {
mOverviewModeBehaviorSupplier.removeObserver(mOverviewModeSupplierCallback);
}
/**
* Initialize the wrapper to listen for the proper notifications.
*/
......@@ -161,7 +178,8 @@ public class EmptyBackgroundViewWrapper {
// 1. There are no tabs in the normal TabModel AND
// 2. Overview mode is not showing AND
// 3. We're in the normal TabModel OR there are no tabs present in either model
return model.getCount() == 0 && !mOverviewModeBehavior.overviewVisible()
return model.getCount() == 0
&& (mOverviewModeBehavior == null || !mOverviewModeBehavior.overviewVisible())
&& (!incognitoSelected || isIncognitoEmpty);
}
}
......@@ -27,7 +27,9 @@ import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeTabUtils;
......@@ -62,9 +64,10 @@ public class AppMenuTest {
*/
public AppMenuHandlerForTest(AppMenuPropertiesDelegate delegate,
AppMenuCoordinator.AppMenuDelegate appMenuDelegate, int menuResourceId,
View decorView, ActivityLifecycleDispatcher activityLifecycleDispatcher) {
super(delegate, appMenuDelegate, menuResourceId, decorView,
activityLifecycleDispatcher);
View decorView, ActivityLifecycleDispatcher activityLifecycleDispatcher,
ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
super(delegate, appMenuDelegate, menuResourceId, decorView, activityLifecycleDispatcher,
overviewModeBehaviorSupplier);
}
@Override
......@@ -79,10 +82,11 @@ public class AppMenuTest {
InstrumentationRegistry.getInstrumentation().setInTouchMode(false);
AppMenuCoordinator.setAppMenuHandlerFactoryForTesting(
(delegate, appMenuDelegate, menuResourceId, decorView,
activityLifecycleDispatcher) -> {
(delegate, appMenuDelegate, menuResourceId, decorView, activityLifecycleDispatcher,
overviewModeBehaviorSupplier) -> {
mAppMenuHandler = new AppMenuHandlerForTest(delegate, appMenuDelegate,
menuResourceId, decorView, activityLifecycleDispatcher);
menuResourceId, decorView, activityLifecycleDispatcher,
overviewModeBehaviorSupplier);
return mAppMenuHandler;
});
......
......@@ -20,8 +20,10 @@ import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeSwitches;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.util.ObservableSupplier;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
......@@ -52,9 +54,10 @@ public class DataSaverAppMenuTest {
*/
public AppMenuHandlerForTest(AppMenuPropertiesDelegate delegate,
AppMenuCoordinator.AppMenuDelegate appMenuDelegate, int menuResourceId,
View decorView, ActivityLifecycleDispatcher activityLifecycleDispatcher) {
super(delegate, appMenuDelegate, menuResourceId, decorView,
activityLifecycleDispatcher);
View decorView, ActivityLifecycleDispatcher activityLifecycleDispatcher,
ObservableSupplier<OverviewModeBehavior> overviewModeBehaviorSupplier) {
super(delegate, appMenuDelegate, menuResourceId, decorView, activityLifecycleDispatcher,
overviewModeBehaviorSupplier);
mDelegate = delegate;
}
......@@ -83,10 +86,11 @@ public class DataSaverAppMenuTest {
@Before
public void setUp() throws Exception {
AppMenuCoordinator.setAppMenuHandlerFactoryForTesting(
(delegate, appMenuDelegate, menuResourceId, decorView,
activityLifecycleDispatcher) -> {
(delegate, appMenuDelegate, menuResourceId, decorView, activityLifecycleDispatcher,
overviewModeBehaviorSupplier) -> {
mAppMenuHandler = new AppMenuHandlerForTest(delegate, appMenuDelegate,
menuResourceId, decorView, activityLifecycleDispatcher);
menuResourceId, decorView, activityLifecycleDispatcher,
overviewModeBehaviorSupplier);
return mAppMenuHandler;
});
......
// Copyright 2019 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.util;
import android.os.Handler;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
/**
* Unit tests for {@link ObservableSupplierImpl}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ObservableSupplierImplTest {
private static final String TEST_STRING_1 = "Test";
private static final String TEST_STRING_2 = "Test2";
private int mCallCount;
private String mLastSuppliedString;
private ObservableSupplierImpl<String> mSupplier = new ObservableSupplierImpl<>();
@Test
public void testObserverNotification_SetMultiple() {
Callback<String> supplierObserver = result -> {
mCallCount++;
mLastSuppliedString = result;
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, null, "before setting first string.");
mSupplier.set(TEST_STRING_1);
checkState(1, TEST_STRING_1, TEST_STRING_1, "after setting first string.");
mSupplier.set(TEST_STRING_2);
checkState(2, TEST_STRING_2, TEST_STRING_2, "after setting second string.");
mSupplier.set(null);
checkState(3, null, null, "after setting third string.");
}
@Test
public void testObserverNotification_SetSame() {
Callback<String> supplierObserver = result -> {
mCallCount++;
mLastSuppliedString = result;
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, null, "before setting first string.");
mSupplier.set(TEST_STRING_1);
checkState(1, TEST_STRING_1, TEST_STRING_1, "after setting first string.");
mSupplier.set(TEST_STRING_1);
checkState(1, TEST_STRING_1, TEST_STRING_1, "after resetting first string.");
}
@Test
public void testObserverNotification_RemoveObserver() {
Callback<String> supplierObserver = result -> {
mCallCount++;
mLastSuppliedString = result;
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, null, "before setting first string.");
mSupplier.set(TEST_STRING_1);
checkState(1, TEST_STRING_1, TEST_STRING_1, "after setting first string.");
mSupplier.removeObserver(supplierObserver);
mSupplier.set(TEST_STRING_2);
checkState(1, TEST_STRING_1, TEST_STRING_2, "after setting second string.");
}
@Test
public void testObserverNotification_RegisterObserverAfterSet() {
Handler handler = new Handler();
handler.post(() -> {
mSupplier.set(TEST_STRING_1);
checkState(0, null, TEST_STRING_1, "after setting first string.");
Callback<String> supplierObserver = new Callback<String>() {
@Override
public void onResult(String result) {
mCallCount++;
mLastSuppliedString = result;
}
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, TEST_STRING_1, "after setting observer.");
});
handler.post(() -> checkState(1, TEST_STRING_1, TEST_STRING_1, "in second message loop."));
}
@Test
public void testObserverNotification_RegisterObserverAfterSetThenSetAgain() {
Handler handler = new Handler();
handler.post(() -> {
mSupplier.set(TEST_STRING_1);
checkState(0, null, TEST_STRING_1, "after setting first string.");
Callback<String> supplierObserver = new Callback<String>() {
@Override
public void onResult(String result) {
mCallCount++;
mLastSuppliedString = result;
}
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, TEST_STRING_1, "after setting observer.");
mSupplier.set(TEST_STRING_2);
checkState(1, TEST_STRING_2, TEST_STRING_2, "after setting second string.");
});
handler.post(() -> checkState(1, TEST_STRING_2, TEST_STRING_2, "in second message loop."));
}
@Test
public void testObserverNotification_RegisterObserverAfterSetThenRemove() {
Handler handler = new Handler();
handler.post(() -> {
mSupplier.set(TEST_STRING_1);
checkState(0, null, TEST_STRING_1, "after setting first string.");
Callback<String> supplierObserver = new Callback<String>() {
@Override
public void onResult(String result) {
mCallCount++;
mLastSuppliedString = result;
}
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, TEST_STRING_1, "after setting observer.");
mSupplier.removeObserver(supplierObserver);
});
handler.post(() -> checkState(0, null, TEST_STRING_1, "in second message loop."));
}
@Test
public void testObserverNotification_RemoveObserverInsideCallback() {
Callback<String> supplierObserver = new Callback<String>() {
@Override
public void onResult(String result) {
mCallCount++;
mLastSuppliedString = result;
mSupplier.removeObserver(this);
}
};
mSupplier.addObserver(supplierObserver);
checkState(0, null, null, "before setting first string.");
mSupplier.set(TEST_STRING_1);
checkState(1, TEST_STRING_1, TEST_STRING_1, "after setting first string.");
mSupplier.set(TEST_STRING_2);
checkState(1, TEST_STRING_1, TEST_STRING_2, "after setting second string.");
}
private void checkState(int expectedCallCount, String expectedLastSuppliedString,
String expectedStringFromGet, String assertDescription) {
Assert.assertEquals(
"Incorrect call count " + assertDescription, expectedCallCount, mCallCount);
Assert.assertEquals("Incorrect last supplied string " + assertDescription,
expectedLastSuppliedString, mLastSuppliedString);
Assert.assertEquals(
"Incorrect #get() " + assertDescription, expectedStringFromGet, mSupplier.get());
}
}
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