blob: 1eb2e5582f0d65b88693a8842ba1c60dcdb93271 [file] [log] [blame]
// 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.features.start_surface;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_CLICKLISTENER;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_HEIGHT;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_SELECTED_TAB_POSITION;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.FEED_SURFACE_COORDINATOR;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_BOTTOM_BAR_VISIBLE;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_EXPLORE_SURFACE_VISIBLE;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SECONDARY_SURFACE_VISIBLE;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.IS_SHOWING_OVERVIEW;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_BAR_HEIGHT;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.feed.FeedSurfaceCoordinator;
import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.start_surface.R;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** The mediator implements the logic to interact with the surfaces and caller. */
class StartSurfaceMediator
implements StartSurface.Controller, TabSwitcher.OverviewModeObserver, View.OnClickListener {
@IntDef({SurfaceMode.NO_START_SURFACE, SurfaceMode.TASKS_ONLY, SurfaceMode.TWO_PANES,
SurfaceMode.SINGLE_PANE})
@Retention(RetentionPolicy.SOURCE)
@interface SurfaceMode {
int NO_START_SURFACE = 0;
int TASKS_ONLY = 1;
int TWO_PANES = 2;
int SINGLE_PANE = 3;
}
/** The delegate to interact with the location bar. */
interface Delegate {
/**
* Add the given url focus change listener.
* @param listener The given listener.
*/
void addUrlFocusChangeListener(UrlFocusChangeListener listener);
/**
* Remove the given url focus change listener.
* @param listener The given listener.
*/
void removeUrlFocusChangeListener(UrlFocusChangeListener listener);
}
/** Interface to initialize a secondary tasks surface for more tabs. */
interface SecondaryTasksSurfaceInitializer {
/**
* Initialize the secondary tasks surface and return the surface controller, which is
* TabSwitcher.Controller.
* @return The {@link TabSwitcher.Controller} of the secondary tasks surface.
*/
TabSwitcher.Controller initialize();
}
private final ObserverList<StartSurface.OverviewModeObserver> mObservers = new ObserverList<>();
private final TabSwitcher.Controller mController;
@Nullable
private final PropertyModel mPropertyModel;
@Nullable
private final ExploreSurfaceCoordinator.FeedSurfaceCreator mFeedSurfaceCreator;
@Nullable
private final SecondaryTasksSurfaceInitializer mSecondaryTasksSurfaceInitializer;
@SurfaceMode
private final int mSurfaceMode;
@Nullable
private TabSwitcher.Controller mSecondaryTasksSurfaceController;
@Nullable
private PropertyModel mSecondaryTasksSurfacePropertyModel;
private boolean mIsIncognito;
@Nullable
private Delegate mDelegate;
@Nullable
UrlFocusChangeListener mUrlFocusChangeListener;
@Nullable
private StartSurface.StateObserver mStateObserver;
StartSurfaceMediator(TabSwitcher.Controller controller, TabModelSelector tabModelSelector,
@Nullable PropertyModel propertyModel,
@Nullable ExploreSurfaceCoordinator.FeedSurfaceCreator feedSurfaceCreator,
@Nullable SecondaryTasksSurfaceInitializer secondaryTasksSurfaceInitializer,
@SurfaceMode int surfaceMode, @Nullable Delegate delegate) {
mController = controller;
mPropertyModel = propertyModel;
mFeedSurfaceCreator = feedSurfaceCreator;
mSecondaryTasksSurfaceInitializer = secondaryTasksSurfaceInitializer;
mSurfaceMode = surfaceMode;
mDelegate = delegate;
if (mPropertyModel != null) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE || mSurfaceMode == SurfaceMode.TWO_PANES
|| mSurfaceMode == SurfaceMode.TASKS_ONLY;
assert mDelegate != null;
mIsIncognito = tabModelSelector.isIncognitoSelected();
tabModelSelector.addObserver(new EmptyTabModelSelectorObserver() {
@Override
public void onTabModelSelected(TabModel newModel, TabModel oldModel) {
// TODO(crbug.com/982018): Optimize to not listen for selected Tab model change
// when overview is not shown.
updateIncognitoMode(newModel.isIncognito());
}
});
mPropertyModel.set(IS_INCOGNITO, mIsIncognito);
mPropertyModel.set(
BOTTOM_BAR_CLICKLISTENER, new StartSurfaceProperties.BottomBarClickListener() {
// TODO(crbug.com/982018): Animate switching between explore and home
// surface.
@Override
public void onHomeButtonClicked() {
setExploreSurfaceVisibility(false);
notifyStateChange();
RecordUserAction.record("StartSurface.TwoPanes.BottomBar.TapHome");
}
@Override
public void onExploreButtonClicked() {
// TODO(crbug.com/982018): Hide the Tab switcher toolbar when showing
// explore surface.
setExploreSurfaceVisibility(true);
notifyStateChange();
RecordUserAction.record(
"StartSurface.TwoPanes.BottomBar.TapExploreSurface");
}
});
if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
mPropertyModel.set(MORE_TABS_CLICK_LISTENER, this);
}
// Set the initial state.
// Show explore surface if not in incognito and either in SINGLE PANES mode
// or in TWO PANES mode with last visible pane explore.
boolean shouldShowExploreSurface =
(mSurfaceMode == SurfaceMode.SINGLE_PANE
|| ReturnToStartSurfaceUtil.shouldShowExploreSurface())
&& !mIsIncognito;
setExploreSurfaceVisibility(shouldShowExploreSurface);
if (mSurfaceMode == SurfaceMode.TWO_PANES) {
mPropertyModel.set(BOTTOM_BAR_HEIGHT,
ContextUtils.getApplicationContext().getResources().getDimensionPixelSize(
R.dimen.ss_bottom_bar_height));
mPropertyModel.set(IS_BOTTOM_BAR_VISIBLE, !mIsIncognito);
}
mPropertyModel.set(MV_TILES_VISIBLE, !mIsIncognito);
int toolbarHeight =
ContextUtils.getApplicationContext().getResources().getDimensionPixelSize(
R.dimen.toolbar_height_no_shadow);
mPropertyModel.set(TOP_BAR_HEIGHT, toolbarHeight);
mUrlFocusChangeListener = new UrlFocusChangeListener() {
@Override
public void onUrlFocusChange(boolean hasFocus) {
// No fake search box on the explore pane in two panes mode.
if (mSurfaceMode != SurfaceMode.TWO_PANES
|| !mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)) {
mPropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, !hasFocus);
}
if (mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE)) {
mSecondaryTasksSurfacePropertyModel.set(
IS_FAKE_SEARCH_BOX_VISIBLE, !hasFocus);
}
notifyStateChange();
}
};
}
mController.addOverviewModeObserver(this);
}
void setSecondaryTasksSurfacePropertyModel(PropertyModel propertyModel) {
mSecondaryTasksSurfacePropertyModel = propertyModel;
mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO, mIsIncognito);
// Secondary tasks surface is used for more Tabs or incognito mode single pane, where MV
// tiles should be invisible.
mSecondaryTasksSurfacePropertyModel.set(MV_TILES_VISIBLE, false);
}
void setStateChangeObserver(StartSurface.StateObserver observer) {
mStateObserver = observer;
}
// Implements StartSurface.Controller
@Override
public boolean overviewVisible() {
return mController.overviewVisible();
}
@Override
public void addOverviewModeObserver(StartSurface.OverviewModeObserver observer) {
mObservers.addObserver(observer);
}
@Override
public void removeOverviewModeObserver(StartSurface.OverviewModeObserver observer) {
mObservers.removeObserver(observer);
}
@Override
public void hideOverview(boolean animate) {
if (mSecondaryTasksSurfaceController != null
&& mSecondaryTasksSurfaceController.overviewVisible()) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE;
setSecondaryTasksSurfaceVisibility(false);
}
mController.hideOverview(animate);
}
@Override
public void showOverview(boolean animate) {
// TODO(crbug.com/982018): Animate the bottom bar together with the Tab Grid view.
if (mPropertyModel != null) {
if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
RecordUserAction.record("StartSurface.SinglePane");
if (mIsIncognito) {
setSecondaryTasksSurfaceVisibility(true);
} else {
setExploreSurfaceVisibility(true);
}
} else if (mSurfaceMode == SurfaceMode.TWO_PANES) {
RecordUserAction.record("StartSurface.TwoPanes");
String defaultOnUserActionString = mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
? "ExploreSurface"
: "HomeSurface";
RecordUserAction.record(
"StartSurface.TwoPanes.DefaultOn" + defaultOnUserActionString);
}
// Make sure FeedSurfaceCoordinator is built before the explore surface is showing by
// default.
if (mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
&& mPropertyModel.get(FEED_SURFACE_COORDINATOR) == null) {
mPropertyModel.set(FEED_SURFACE_COORDINATOR,
mFeedSurfaceCreator.createFeedSurfaceCoordinator());
}
mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
mDelegate.addUrlFocusChangeListener(mUrlFocusChangeListener);
}
mController.showOverview(animate);
}
@Override
public boolean onBackPressed() {
if (mSecondaryTasksSurfaceController != null
&& mSecondaryTasksSurfaceController.overviewVisible()
// Secondary tasks surface is used as the main surface in incognito mode.
&& !mIsIncognito) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE;
setSecondaryTasksSurfaceVisibility(false);
setExploreSurfaceVisibility(true);
notifyStateChange();
return true;
}
if (mPropertyModel != null && mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
&& mSurfaceMode == SurfaceMode.TWO_PANES) {
setExploreSurfaceVisibility(false);
notifyStateChange();
return true;
}
return mController.onBackPressed();
}
// Implements TabSwitcher.OverviewModeObserver.
@Override
public void startedShowing() {
for (StartSurface.OverviewModeObserver observer : mObservers) {
observer.startedShowing();
}
}
@Override
public void finishedShowing() {
for (StartSurface.OverviewModeObserver observer : mObservers) {
observer.finishedShowing();
}
// TODO(crbug.com/982018): Move to showOverview before overview is showing.
// Note that Tab switcher mode toolbar is lazily created when showing Tab switcher the first
// time.
if (mSurfaceMode != SurfaceMode.NO_START_SURFACE) {
notifyStateChange();
}
}
@Override
public void startedHiding() {
if (mPropertyModel != null) {
mPropertyModel.set(IS_SHOWING_OVERVIEW, false);
mDelegate.removeUrlFocusChangeListener(mUrlFocusChangeListener);
destroyFeedSurfaceCoordinator();
}
for (StartSurface.OverviewModeObserver observer : mObservers) {
observer.startedHiding();
}
}
@Override
public void finishedHiding() {
for (StartSurface.OverviewModeObserver observer : mObservers) {
observer.finishedHiding();
}
}
private void destroyFeedSurfaceCoordinator() {
FeedSurfaceCoordinator feedSurfaceCoordinator =
mPropertyModel.get(FEED_SURFACE_COORDINATOR);
if (feedSurfaceCoordinator != null) feedSurfaceCoordinator.destroy();
mPropertyModel.set(FEED_SURFACE_COORDINATOR, null);
}
// Implements View.OnClickListener, which listens for the more tabs button.
@Override
public void onClick(View v) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE;
if (mSecondaryTasksSurfaceController == null) {
mSecondaryTasksSurfaceController = mSecondaryTasksSurfaceInitializer.initialize();
assert mSecondaryTasksSurfacePropertyModel != null;
}
setExploreSurfaceVisibility(false);
setSecondaryTasksSurfaceVisibility(true);
RecordUserAction.record("StartSurface.SinglePane.MoreTabs");
}
/** This interface builds the feed surface coordinator when showing if needed. */
private void setExploreSurfaceVisibility(boolean isVisible) {
if (isVisible == mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)) return;
if (isVisible && mPropertyModel.get(IS_SHOWING_OVERVIEW)
&& mPropertyModel.get(FEED_SURFACE_COORDINATOR) == null) {
mPropertyModel.set(
FEED_SURFACE_COORDINATOR, mFeedSurfaceCreator.createFeedSurfaceCoordinator());
}
mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, isVisible);
if (mSurfaceMode == SurfaceMode.TWO_PANES) {
// Update the 'BOTTOM_BAR_SELECTED_TAB_POSITION' property to reflect the change. This is
// needed when clicking back button on the explore surface.
mPropertyModel.set(BOTTOM_BAR_SELECTED_TAB_POSITION, isVisible ? 1 : 0);
ReturnToStartSurfaceUtil.setExploreSurfaceVisibleLast(isVisible);
}
}
private void updateIncognitoMode(boolean isIncognito) {
if (isIncognito == mIsIncognito) return;
mIsIncognito = isIncognito;
mPropertyModel.set(MV_TILES_VISIBLE, !mIsIncognito);
if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
setExploreSurfaceVisibility(!mIsIncognito);
setSecondaryTasksSurfaceVisibility(
mIsIncognito && mPropertyModel.get(IS_SHOWING_OVERVIEW));
} else if (mSurfaceMode == SurfaceMode.TWO_PANES) {
mPropertyModel.set(BOTTOM_BAR_HEIGHT,
mIsIncognito ? 0
: ContextUtils.getApplicationContext()
.getResources()
.getDimensionPixelSize(R.dimen.ss_bottom_bar_height));
mPropertyModel.set(IS_BOTTOM_BAR_VISIBLE, !mIsIncognito);
// Hide explore surface if going incognito. When returning to normal mode, we
// always show the Home Pane, so the Explore Pane stays hidden.
if (mIsIncognito) setExploreSurfaceVisibility(false);
}
mPropertyModel.set(IS_INCOGNITO, mIsIncognito);
if (mSecondaryTasksSurfacePropertyModel != null) {
mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO, mIsIncognito);
}
notifyStateChange();
}
private void setSecondaryTasksSurfaceVisibility(boolean isVisible) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE;
if (isVisible) {
if (mSecondaryTasksSurfaceController == null) {
mSecondaryTasksSurfaceController = mSecondaryTasksSurfaceInitializer.initialize();
}
mSecondaryTasksSurfacePropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, mIsIncognito);
mSecondaryTasksSurfaceController.showOverview(false);
} else {
if (mSecondaryTasksSurfaceController == null) return;
mSecondaryTasksSurfaceController.hideOverview(false);
}
mPropertyModel.set(IS_SECONDARY_SURFACE_VISIBLE, isVisible);
}
private void notifyStateChange() {
assert mSurfaceMode != SurfaceMode.NO_START_SURFACE;
if (mStateObserver != null) {
mStateObserver.onStateChanged(shouldShowTabSwitcherToolbar());
}
}
private boolean shouldShowTabSwitcherToolbar() {
if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
// Show Tab switcher toolbar when showing more Tabs and in incognito single pane when
// fake search box is visible.
if (mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE)) {
return !mIsIncognito
|| (mIsIncognito
&& mSecondaryTasksSurfacePropertyModel.get(
IS_FAKE_SEARCH_BOX_VISIBLE));
}
}
// Do not show Tab switcher toolbar on explore pane.
if (mSurfaceMode == SurfaceMode.TWO_PANES
&& mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)) {
return false;
}
// Do not show Tab switcher toolbar when focusing the Omnibox.
return mPropertyModel.get(IS_FAKE_SEARCH_BOX_VISIBLE);
}
}