blob: a7dbdc2caafb83fe47068e295ce83f921cba4065 [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.IS_INCOGNITO_DESCRIPTION_INITIALIZED;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_VISIBLE;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_SURFACE_BODY_VISIBLE;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_TAB_CAROUSEL_VISIBLE;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MORE_TABS_CLICK_LISTENER;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TAB_SWITCHER_TITLE_TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN;
import static org.chromium.chrome.browser.tasks.TasksSurfaceProperties.TRENDY_TERMS_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.IS_SHOWING_STACK_TAB_SWITCHER;
import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN;
import android.content.res.Resources;
import android.view.View;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ContextUtils;
import org.chromium.base.ObserverList;
import org.chromium.base.StrictModeContext;
import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeState;
import org.chromium.chrome.browser.feed.FeedSurfaceCoordinator;
import org.chromium.chrome.browser.feed.shared.stream.Stream;
import org.chromium.chrome.browser.flags.CachedFeatureFlags;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.night_mode.NightModeStateProvider;
import org.chromium.chrome.browser.ntp.FakeboxDelegate;
import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver;
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.tasks.pseudotab.PseudoTab;
import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher;
import org.chromium.chrome.start_surface.R;
import org.chromium.components.prefs.PrefService;
import org.chromium.ui.modelutil.PropertyModel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/** 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, SurfaceMode.OMNIBOX_ONLY, SurfaceMode.TRENDY_TERMS})
@Retention(RetentionPolicy.SOURCE)
@interface SurfaceMode {
int NO_START_SURFACE = 0;
int TASKS_ONLY = 1;
int TWO_PANES = 2;
int SINGLE_PANE = 3;
int OMNIBOX_ONLY = 4;
int TRENDY_TERMS = 5;
}
/** 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();
}
/**
* Interface to check the associated activity state.
*/
interface ActivityStateChecker {
/**
* @return Whether the associated activity is finishing or destroyed.
*/
boolean isFinishingOrDestroyed();
}
private final ObserverList<StartSurface.OverviewModeObserver> mObservers = new ObserverList<>();
private final TabSwitcher.Controller mController;
private final TabModelSelector mTabModelSelector;
@Nullable
private final PropertyModel mPropertyModel;
@Nullable
private final SecondaryTasksSurfaceInitializer mSecondaryTasksSurfaceInitializer;
@SurfaceMode
private final int mSurfaceMode;
// Boolean histogram used to record whether cached
// ChromePreferenceKeys.FEED_ARTICLES_LIST_VISIBLE is consistent with
// Pref.ARTICLES_LIST_VISIBLE.
@VisibleForTesting
static final String FEED_VISIBILITY_CONSISTENCY =
"Startup.Android.CachedFeedVisibilityConsistency";
@Nullable
private ExploreSurfaceCoordinator.FeedSurfaceCreator mFeedSurfaceCreator;
@Nullable
private TabSwitcher.Controller mSecondaryTasksSurfaceController;
@Nullable
private PropertyModel mSecondaryTasksSurfacePropertyModel;
private boolean mIsIncognito;
@Nullable
private FakeboxDelegate mFakeboxDelegate;
private NightModeStateProvider mNightModeStateProvider;
@Nullable
UrlFocusChangeListener mUrlFocusChangeListener;
@Nullable
private StartSurface.StateObserver mStateObserver;
@OverviewModeState
private int mOverviewModeState;
@OverviewModeState
private int mPreviousOverviewModeState;
private boolean mIsOmniboxFocused;
@Nullable
private TabModel mNormalTabModel;
@Nullable
private TabModelObserver mNormalTabModelObserver;
@Nullable
private TabModelSelectorObserver mTabModelSelectorObserver;
private BrowserControlsStateProvider mBrowserControlsStateProvider;
private BrowserControlsStateProvider.Observer mBrowserControlsObserver;
private ActivityStateChecker mActivityStateChecker;
private boolean mExcludeMVTiles;
private boolean mShowStackTabSwitcher;
/**
* The value of {@link Pref#ARTICLES_LIST_VISIBLE} on Startup. Getting this value for recording
* the consistency of {@link ChromePreferenceKeys#FEED_ARTICLES_LIST_VISIBLE} with {@link
* Pref#ARTICLES_LIST_VISIBLE}.
*/
private Boolean mFeedVisibilityPrefOnStartUp;
/**
* The value of {@link ChromePreferenceKeys#FEED_ARTICLES_LIST_VISIBLE} on Startup. Getting this
* value for recording the consistency with {@link Pref#ARTICLES_LIST_VISIBLE}.
*/
@Nullable
private Boolean mFeedVisibilityInSharedPreferenceOnStartUp;
StartSurfaceMediator(TabSwitcher.Controller controller, TabModelSelector tabModelSelector,
@Nullable PropertyModel propertyModel,
@Nullable SecondaryTasksSurfaceInitializer secondaryTasksSurfaceInitializer,
@SurfaceMode int surfaceMode, NightModeStateProvider nightModeStateProvider,
BrowserControlsStateProvider browserControlsStateProvider,
ActivityStateChecker activityStateChecker, boolean excludeMVTiles,
boolean showStackTabSwitcher) {
mController = controller;
mTabModelSelector = tabModelSelector;
mPropertyModel = propertyModel;
mSecondaryTasksSurfaceInitializer = secondaryTasksSurfaceInitializer;
mSurfaceMode = surfaceMode;
mNightModeStateProvider = nightModeStateProvider;
mBrowserControlsStateProvider = browserControlsStateProvider;
mActivityStateChecker = activityStateChecker;
mExcludeMVTiles = excludeMVTiles;
mShowStackTabSwitcher = showStackTabSwitcher;
if (mPropertyModel != null) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE || mSurfaceMode == SurfaceMode.TWO_PANES
|| mSurfaceMode == SurfaceMode.TASKS_ONLY
|| mSurfaceMode == SurfaceMode.OMNIBOX_ONLY
|| mSurfaceMode == SurfaceMode.TRENDY_TERMS;
mIsIncognito = mTabModelSelector.isIncognitoSelected();
mTabModelSelectorObserver = 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);
if (mSurfaceMode == SurfaceMode.TWO_PANES) {
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);
// Hide tab carousel, which does not exist in incognito mode, when closing all
// normal tabs.
mNormalTabModel = mTabModelSelector.getModel(false);
mNormalTabModelObserver = new TabModelObserver() {
@Override
public void willCloseTab(Tab tab, boolean animate) {
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE
&& mNormalTabModel.getCount() <= 1) {
setTabCarouselVisibility(false);
}
}
@Override
public void tabClosureUndone(Tab tab) {
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE) {
setTabCarouselVisibility(true);
}
}
@Override
public void restoreCompleted() {
if (!(mPropertyModel.get(IS_SHOWING_OVERVIEW)
&& mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE)) {
return;
}
setTabCarouselVisibility(
mTabModelSelector.getModel(false).getCount() > 0 && !mIsIncognito);
}
};
}
mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
@Override
public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
mPropertyModel.set(
TOP_MARGIN, mBrowserControlsStateProvider.getContentOffset());
}
@Override
public void onBottomControlsHeightChanged(
int bottomControlsHeight, int bottomControlsMinHeight) {
// Only pad single pane home page since tabs grid has already been
// padded for the bottom bar.
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE) {
mPropertyModel.set(BOTTOM_BAR_HEIGHT, bottomControlsHeight);
}
}
};
mUrlFocusChangeListener = new UrlFocusChangeListener() {
@Override
public void onUrlFocusChange(boolean hasFocus) {
if (hasFakeSearchBox()) {
if (mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE)) {
mSecondaryTasksSurfacePropertyModel.set(
IS_FAKE_SEARCH_BOX_VISIBLE, !hasFocus);
} else {
setFakeBoxVisibility(!hasFocus);
}
}
notifyStateChange();
}
};
// Only tweak the margins between sections for non-OMNIBOX_ONLY and non-TRENDY_TERMS
// variations.
if (surfaceMode != SurfaceMode.OMNIBOX_ONLY
&& surfaceMode != SurfaceMode.TRENDY_TERMS) {
Resources resources = ContextUtils.getApplicationContext().getResources();
mPropertyModel.set(TASKS_SURFACE_BODY_TOP_MARGIN,
resources.getDimensionPixelSize(R.dimen.tasks_surface_body_top_margin));
mPropertyModel.set(MV_TILES_CONTAINER_TOP_MARGIN,
resources.getDimensionPixelSize(R.dimen.mv_tiles_container_top_margin));
mPropertyModel.set(TAB_SWITCHER_TITLE_TOP_MARGIN,
resources.getDimensionPixelSize(R.dimen.tab_switcher_title_top_margin));
}
}
mController.addOverviewModeObserver(this);
mPreviousOverviewModeState = OverviewModeState.NOT_SHOWN;
mOverviewModeState = OverviewModeState.NOT_SHOWN;
}
void initWithNative(@Nullable FakeboxDelegate fakeboxDelegate,
@Nullable ExploreSurfaceCoordinator.FeedSurfaceCreator feedSurfaceCreator,
PrefService prefService) {
mFakeboxDelegate = fakeboxDelegate;
mFeedSurfaceCreator = feedSurfaceCreator;
if (mPropertyModel != null) {
assert mFakeboxDelegate != null;
// Initialize
// Note that isVoiceSearchEnabled will return false in incognito mode.
mPropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE,
mFakeboxDelegate.getVoiceRecognitionHandler().isVoiceSearchEnabled());
if (mController.overviewVisible()) {
mFakeboxDelegate.addUrlFocusChangeListener(mUrlFocusChangeListener);
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE
&& mFeedSurfaceCreator != null) {
setExploreSurfaceVisibility(!mIsIncognito);
}
}
// Cache the preference on whether the placeholder of Feed is dense. If it's in
// landscape mode, the placeholder should always show in dense mode. Otherwise, whether
// the placeholder is dense depends on whether the first article card of Feed is dense.
FeedSurfaceCoordinator feedSurfaceCoordinator =
mPropertyModel.get(FEED_SURFACE_COORDINATOR);
if (feedSurfaceCoordinator != null) {
Stream feedStream = feedSurfaceCoordinator.getStream();
if (feedStream != null) {
feedStream.addOnContentChangedListener(() -> {
int firstCardDensity = feedStream.getFirstCardDensity();
if (firstCardDensity != Stream.FeedFirstCardDensity.UNKNOWN) {
StartSurfaceConfiguration.setFeedPlaceholderDense(
feedStream.getFirstCardDensity()
== Stream.FeedFirstCardDensity.DENSE);
}
});
}
}
}
mFeedVisibilityPrefOnStartUp = prefService.getBoolean(Pref.ARTICLES_LIST_VISIBLE);
}
boolean isShowingTabSwitcher() {
return mOverviewModeState == OverviewModeState.SHOWING_TABSWITCHER
|| mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER;
}
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 and voice recognition button should be invisible.
mSecondaryTasksSurfacePropertyModel.set(MV_TILES_VISIBLE, false);
mSecondaryTasksSurfacePropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE, false);
}
void setStateChangeObserver(StartSurface.StateObserver observer) {
mStateObserver = observer;
}
// Implements StartSurface.Controller
@Override
public boolean overviewVisible() {
return mController.overviewVisible();
}
@Override
public void setOverviewState(@OverviewModeState int state) {
// TODO(crbug.com/1039691): Refactor into state and trigger to separate SHOWING and SHOWN
// states.
if (mPropertyModel == null || state == mOverviewModeState) return;
// Cache previous state.
if (mOverviewModeState != OverviewModeState.NOT_SHOWN) {
mPreviousOverviewModeState = mOverviewModeState;
}
mOverviewModeState = state;
setOverviewStateInternal();
// Immediately transition from SHOWING to SHOWN state if overview is visible but state not
// SHOWN. This is only necessary when the new state is a SHOWING state.
if (mPropertyModel.get(IS_SHOWING_OVERVIEW)
&& mOverviewModeState != OverviewModeState.NOT_SHOWN
&& !isShownState(mOverviewModeState)) {
// Compute SHOWN state before updating previous state, because the previous state is
// still needed to compute the shown state.
@OverviewModeState
int shownState = computeOverviewStateShown();
// Cache previous state
mPreviousOverviewModeState = mOverviewModeState;
mOverviewModeState = shownState;
setOverviewStateInternal();
}
notifyStateChange();
// Metrics collection
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE) {
RecordUserAction.record("StartSurface.SinglePane.Home");
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER) {
RecordUserAction.record("StartSurface.SinglePane.Tabswitcher");
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES) {
RecordUserAction.record("StartSurface.TwoPanes");
String defaultOnUserActionString = mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
? "ExploreSurface"
: "HomeSurface";
RecordUserAction.record("StartSurface.TwoPanes.DefaultOn" + defaultOnUserActionString);
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY) {
RecordUserAction.record("StartSurface.TasksOnly");
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_OMNIBOX_ONLY) {
RecordUserAction.record("StartSurface.OmniboxOnly");
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TRENDY_TERMS) {
RecordUserAction.record("StartSurface.TrendyTerms");
}
}
private void setOverviewStateInternal() {
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE) {
mPropertyModel.set(IS_SHOWING_STACK_TAB_SWITCHER, false);
setExploreSurfaceVisibility(!mIsIncognito && mFeedSurfaceCreator != null);
boolean hasNormalTab;
if (CachedFeatureFlags.isEnabled(ChromeFeatureList.INSTANT_START)
&& !mTabModelSelector.isTabStateInitialized()) {
List<PseudoTab> allTabs;
try (StrictModeContext ignored = StrictModeContext.allowDiskReads()) {
allTabs = PseudoTab.getAllPseudoTabsFromStateFile();
}
hasNormalTab = allTabs != null && !allTabs.isEmpty();
} else {
hasNormalTab = mTabModelSelector.getModel(false).getCount() > 0;
}
setTabCarouselVisibility(hasNormalTab && !mIsIncognito);
setMVTilesVisibility(!mIsIncognito);
setFakeBoxVisibility(!mIsIncognito);
setSecondaryTasksSurfaceVisibility(mIsIncognito);
// Only pad single pane home page since tabs grid has already been padding for the
// bottom bar.
mPropertyModel.set(
BOTTOM_BAR_HEIGHT, mBrowserControlsStateProvider.getBottomControlsHeight());
mNormalTabModel.addObserver(mNormalTabModelObserver);
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER) {
mPropertyModel.set(IS_SHOWING_STACK_TAB_SWITCHER, mShowStackTabSwitcher);
setTabCarouselVisibility(false);
setMVTilesVisibility(false);
setFakeBoxVisibility(false);
setSecondaryTasksSurfaceVisibility(true);
setExploreSurfaceVisibility(false);
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES) {
// Show Explore Surface if last visible pane explore.
setExploreSurfaceVisibility(
ReturnToStartSurfaceUtil.shouldShowExploreSurface() && !mIsIncognito);
setMVTilesVisibility(!mIsIncognito);
mPropertyModel.set(BOTTOM_BAR_HEIGHT,
mIsIncognito ? 0
: ContextUtils.getApplicationContext()
.getResources()
.getDimensionPixelSize(R.dimen.ss_bottom_bar_height));
mPropertyModel.set(IS_BOTTOM_BAR_VISIBLE, !mIsIncognito);
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY) {
setMVTilesVisibility(!mIsIncognito);
setExploreSurfaceVisibility(false);
setFakeBoxVisibility(true);
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_OMNIBOX_ONLY) {
setMVTilesVisibility(false);
setExploreSurfaceVisibility(false);
setFakeBoxVisibility(true);
} else if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TRENDY_TERMS) {
setMVTilesVisibility(false);
setExploreSurfaceVisibility(false);
setFakeBoxVisibility(true);
setTrendyTermsVisibility(true);
} else if (mOverviewModeState == OverviewModeState.NOT_SHOWN) {
if (mSecondaryTasksSurfacePropertyModel != null) {
setSecondaryTasksSurfaceVisibility(false);
}
}
if (isShownState(mOverviewModeState)) {
setIncognitoModeDescriptionVisibility(mIsIncognito
&& (mTabModelSelector.getModel(true).getCount() <= 0 || mShowStackTabSwitcher));
}
}
@VisibleForTesting
@OverviewModeState
public int getOverviewState() {
return mOverviewModeState;
}
@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) {
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) {
RecordUserAction.record("StartSurface.Shown");
// update incognito
mIsIncognito = mTabModelSelector.isIncognitoSelected();
mPropertyModel.set(IS_INCOGNITO, mIsIncognito);
// if OvervieModeState is NOT_SHOWN, default to SHOWING_TABSWITCHER. This should only
// happen when entering Start through SwipeDown gesture on URL bar.
if (mOverviewModeState == OverviewModeState.NOT_SHOWN) {
mOverviewModeState = OverviewModeState.SHOWING_TABSWITCHER;
}
// set OverviewModeState
@OverviewModeState
int shownState = computeOverviewStateShown();
assert (isShownState(shownState));
setOverviewState(shownState);
// 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
&& !mActivityStateChecker.isFinishingOrDestroyed()
&& mFeedSurfaceCreator != null) {
mPropertyModel.set(FEED_SURFACE_COORDINATOR,
mFeedSurfaceCreator.createFeedSurfaceCoordinator(
mNightModeStateProvider.isInNightMode(),
shouldShowFeedPlaceholder()));
}
mTabModelSelector.addObserver(mTabModelSelectorObserver);
if (mBrowserControlsObserver != null) {
mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver);
}
mPropertyModel.set(TOP_MARGIN, mBrowserControlsStateProvider.getTopControlsHeight());
mPropertyModel.set(IS_SHOWING_OVERVIEW, true);
if (mFakeboxDelegate != null) {
mFakeboxDelegate.addUrlFocusChangeListener(mUrlFocusChangeListener);
}
}
mController.showOverview(animate);
}
@Override
public boolean onBackPressed() {
if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER
// Secondary tasks surface is used as the main surface in incognito mode.
&& !mIsIncognito) {
// If we reached tabswitcher from HomePage.
if (mPreviousOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE) {
setOverviewState(OverviewModeState.SHOWN_HOMEPAGE);
return true;
}
}
if (mPropertyModel != null && mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)
&& mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES) {
setExploreSurfaceVisibility(false);
notifyStateChange();
return true;
}
return mController.onBackPressed();
}
@Override
public void enableRecordingFirstMeaningfulPaint(long activityCreateTimeMs) {
mController.enableRecordingFirstMeaningfulPaint(activityCreateTimeMs);
}
void onOverviewShownAtLaunch(long activityCreationTimeMs) {
mController.onOverviewShownAtLaunch(activityCreationTimeMs);
if (mPropertyModel != null) {
FeedSurfaceCoordinator feedSurfaceCoordinator =
mPropertyModel.get(FEED_SURFACE_COORDINATOR);
if (feedSurfaceCoordinator != null) {
feedSurfaceCoordinator.onOverviewShownAtLaunch(activityCreationTimeMs);
}
}
assert mFeedVisibilityInSharedPreferenceOnStartUp != null;
if (mFeedVisibilityPrefOnStartUp != null) {
RecordHistogram.recordBooleanHistogram(FEED_VISIBILITY_CONSISTENCY,
mFeedVisibilityPrefOnStartUp.equals(
mFeedVisibilityInSharedPreferenceOnStartUp));
}
}
// 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();
}
}
@Override
public void startedHiding() {
if (mPropertyModel != null) {
if (mFakeboxDelegate != null) {
mFakeboxDelegate.removeUrlFocusChangeListener(mUrlFocusChangeListener);
}
mPropertyModel.set(IS_SHOWING_OVERVIEW, false);
destroyFeedSurfaceCoordinator();
if (mNormalTabModelObserver != null) {
mNormalTabModel.removeObserver(mNormalTabModelObserver);
}
if (mTabModelSelectorObserver != null) {
mTabModelSelector.removeObserver(mTabModelSelectorObserver);
}
if (mBrowserControlsObserver != null) {
mBrowserControlsStateProvider.removeObserver(mBrowserControlsObserver);
}
setOverviewState(OverviewModeState.NOT_SHOWN);
RecordUserAction.record("StartSurface.Hidden");
}
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);
}
// TODO(crbug.com/982018): turn into onClickMoreTabs() and hide the OnClickListener signature
// inside. Implements View.OnClickListener, which listens for the more tabs button.
@Override
public void onClick(View v) {
assert mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE;
if (mSecondaryTasksSurfacePropertyModel == null && !mShowStackTabSwitcher) {
mSecondaryTasksSurfaceController = mSecondaryTasksSurfaceInitializer.initialize();
assert mSecondaryTasksSurfacePropertyModel != null;
}
RecordUserAction.record("StartSurface.SinglePane.MoreTabs");
setOverviewState(OverviewModeState.SHOWN_TABSWITCHER);
}
public boolean shouldShowFeedPlaceholder() {
if (mFeedVisibilityInSharedPreferenceOnStartUp == null) {
mFeedVisibilityInSharedPreferenceOnStartUp =
StartSurfaceConfiguration.getFeedArticlesVisibility();
}
// ChromeFeatureList.INTEREST_FEED_V2 is checked directly with ChromeFeatureList#isEnabled()
// in other places. Using CachedFeatureFlags#isEnabled here is deliberate for a pre-native
// check. This mismatch is acceptable, because in our use case in
// FeedSurfaceCoordinator#createStream, we check both versions to avoid the broken UI.
return mSurfaceMode == SurfaceMode.SINGLE_PANE
&& CachedFeatureFlags.isEnabled(ChromeFeatureList.INSTANT_START)
&& StartSurfaceConfiguration.getFeedArticlesVisibility()
&& !CachedFeatureFlags.isEnabled(ChromeFeatureList.INTEREST_FEED_V2);
}
/** 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
&& !mActivityStateChecker.isFinishingOrDestroyed()) {
mPropertyModel.set(FEED_SURFACE_COORDINATOR,
mFeedSurfaceCreator.createFeedSurfaceCoordinator(
mNightModeStateProvider.isInNightMode(), shouldShowFeedPlaceholder()));
}
mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, isVisible);
if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_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(IS_INCOGNITO, mIsIncognito);
setOverviewStateInternal();
// TODO(crbug.com/1021399): This looks not needed since there is no way to change incognito
// mode when focusing on the omnibox and incognito mode change won't affect the visibility
// of the tab switcher toolbar.
if (mPropertyModel.get(IS_SHOWING_OVERVIEW)) notifyStateChange();
}
private void setSecondaryTasksSurfaceVisibility(boolean isVisible) {
assert mSurfaceMode == SurfaceMode.SINGLE_PANE;
if (isVisible) {
if (mSecondaryTasksSurfacePropertyModel == null) {
mSecondaryTasksSurfaceController = mSecondaryTasksSurfaceInitializer.initialize();
}
if (mSecondaryTasksSurfacePropertyModel != null) {
mSecondaryTasksSurfacePropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE,
mIsIncognito && mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE);
mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO, mIsIncognito);
}
if (mSecondaryTasksSurfaceController != null) {
mSecondaryTasksSurfaceController.showOverview(false);
}
} else {
if (mSecondaryTasksSurfaceController != null) {
mSecondaryTasksSurfaceController.hideOverview(false);
}
}
mPropertyModel.set(IS_SECONDARY_SURFACE_VISIBLE, isVisible);
}
private void notifyStateChange() {
if (mStateObserver != null) {
mStateObserver.onStateChanged(mOverviewModeState, shouldShowTabSwitcherToolbar());
}
}
private boolean hasFakeSearchBox() {
// No fake search box on the explore pane in two panes mode.
if (mOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE
|| mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY
|| mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_OMNIBOX_ONLY
|| mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TRENDY_TERMS
|| (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES
&& !mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE))) {
return true;
}
return false;
}
@VisibleForTesting
public boolean shouldShowTabSwitcherToolbar() {
// Always show in TABSWITCHER
if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER) return true;
// Never show on explore pane.
if (mOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES
&& mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE)) {
return false;
}
if (mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE)) {
// Always show on the stack tab switcher secondary surface.
if (mSecondaryTasksSurfacePropertyModel == null) return true;
// Hide when focusing the Omnibox on the secondary surface.
return mSecondaryTasksSurfacePropertyModel.get(IS_FAKE_SEARCH_BOX_VISIBLE);
}
// Hide when focusing the Omnibox on the primary surface.
return mPropertyModel.get(IS_FAKE_SEARCH_BOX_VISIBLE);
}
private void setTabCarouselVisibility(boolean isVisible) {
if (isVisible == mPropertyModel.get(IS_TAB_CAROUSEL_VISIBLE)) return;
mPropertyModel.set(IS_TAB_CAROUSEL_VISIBLE, isVisible);
}
private void setMVTilesVisibility(boolean isVisible) {
if (mExcludeMVTiles || isVisible == mPropertyModel.get(MV_TILES_VISIBLE)) return;
mPropertyModel.set(MV_TILES_VISIBLE, isVisible);
}
private void setTrendyTermsVisibility(boolean isVisible) {
if (isVisible == mPropertyModel.get(TRENDY_TERMS_VISIBLE)) return;
mPropertyModel.set(TRENDY_TERMS_VISIBLE, isVisible);
}
private void setFakeBoxVisibility(boolean isVisible) {
if (mPropertyModel == null) return;
mPropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, isVisible);
// This is because VoiceRecognitionHandler monitors incognito mode and returns
// false in incognito mode. However, when switching incognito mode, this class is notified
// earlier than the VoiceRecognitionHandler, so isVoiceSearchEnabled returns
// incorrect state if check synchronously.
ThreadUtils.postOnUiThread(() -> {
if (mFakeboxDelegate != null && mFakeboxDelegate.getVoiceRecognitionHandler() != null) {
mPropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE,
mFakeboxDelegate.getVoiceRecognitionHandler().isVoiceSearchEnabled());
}
});
}
private void setIncognitoModeDescriptionVisibility(boolean isVisible) {
if (isVisible == mPropertyModel.get(IS_INCOGNITO_DESCRIPTION_VISIBLE)) return;
if (!mPropertyModel.get(IS_INCOGNITO_DESCRIPTION_INITIALIZED)) {
mPropertyModel.set(IS_INCOGNITO_DESCRIPTION_INITIALIZED, true);
}
mPropertyModel.set(IS_INCOGNITO_DESCRIPTION_VISIBLE, isVisible);
mPropertyModel.set(IS_SURFACE_BODY_VISIBLE, !isVisible);
if (mSecondaryTasksSurfacePropertyModel != null) {
if (!mSecondaryTasksSurfacePropertyModel.get(IS_INCOGNITO_DESCRIPTION_INITIALIZED)) {
mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO_DESCRIPTION_INITIALIZED, true);
}
mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO_DESCRIPTION_VISIBLE, isVisible);
mSecondaryTasksSurfacePropertyModel.set(IS_SURFACE_BODY_VISIBLE, !isVisible);
}
}
@OverviewModeState
private int computeOverviewStateShown() {
if (mSurfaceMode == SurfaceMode.SINGLE_PANE) {
if (mOverviewModeState == OverviewModeState.SHOWING_PREVIOUS) {
assert mPreviousOverviewModeState == OverviewModeState.SHOWN_HOMEPAGE
|| mPreviousOverviewModeState == OverviewModeState.SHOWN_TABSWITCHER
|| mPreviousOverviewModeState == OverviewModeState.NOT_SHOWN;
// This class would be re-instantiated after changing theme, then
// mPreviousOverviewModeState will be reset to OverviewModeState.NOT_SHOWN. We
// default to OverviewModeState.SHOWN_HOMEPAGE in this case when SHOWING_PREVIOUS.
return mPreviousOverviewModeState == OverviewModeState.NOT_SHOWN
? OverviewModeState.SHOWN_HOMEPAGE
: mPreviousOverviewModeState;
} else if (mOverviewModeState == OverviewModeState.SHOWING_START) {
return OverviewModeState.SHOWN_HOMEPAGE;
} else if (mOverviewModeState == OverviewModeState.SHOWING_TABSWITCHER) {
return OverviewModeState.SHOWN_TABSWITCHER;
} else if (mOverviewModeState == OverviewModeState.SHOWING_HOMEPAGE) {
return OverviewModeState.SHOWN_HOMEPAGE;
} else {
assert (isShownState(mOverviewModeState)
|| mOverviewModeState == OverviewModeState.NOT_SHOWN);
return mOverviewModeState;
}
}
if (mSurfaceMode == SurfaceMode.TWO_PANES) {
return OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES;
}
if (mSurfaceMode == SurfaceMode.TASKS_ONLY) {
return OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY;
}
if (mSurfaceMode == SurfaceMode.OMNIBOX_ONLY) {
return OverviewModeState.SHOWN_TABSWITCHER_OMNIBOX_ONLY;
}
if (mSurfaceMode == SurfaceMode.TRENDY_TERMS) {
return OverviewModeState.SHOWN_TABSWITCHER_TRENDY_TERMS;
}
return OverviewModeState.DISABLED;
}
private boolean isShownState(@OverviewModeState int state) {
return state == OverviewModeState.SHOWN_HOMEPAGE
|| state == OverviewModeState.SHOWN_TABSWITCHER
|| state == OverviewModeState.SHOWN_TABSWITCHER_TWO_PANES
|| state == OverviewModeState.SHOWN_TABSWITCHER_TASKS_ONLY
|| state == OverviewModeState.SHOWN_TABSWITCHER_OMNIBOX_ONLY
|| state == OverviewModeState.SHOWN_TABSWITCHER_TRENDY_TERMS;
}
}