| // Copyright 2019 The Chromium Authors |
| // 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.features.start_surface.StartSurfaceProperties.BOTTOM_BAR_HEIGHT; |
| import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.EXPLORE_SURFACE_COORDINATOR; |
| 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.RESET_FEED_SURFACE_SCROLL_POSITION; |
| import static org.chromium.chrome.features.start_surface.StartSurfaceProperties.TOP_MARGIN; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.BACKGROUND_COLOR; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_CLICK_LISTENER; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.FAKE_SEARCH_BOX_TEXT_WATCHER; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_FAKE_SEARCH_BOX_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_INCOGNITO; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_INITIALIZED; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_INCOGNITO_DESCRIPTION_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_LENS_BUTTON_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_SURFACE_BODY_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_TAB_CARD_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.IS_VOICE_RECOGNITION_BUTTON_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.LENS_BUTTON_CLICK_LISTENER; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_LEFT_RIGHT_MARGIN; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_CONTAINER_TOP_MARGIN; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.MV_TILES_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.QUERY_TILES_VISIBLE; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.RESET_TASK_SURFACE_HEADER_SCROLL_POSITION; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.SINGLE_TAB_TOP_MARGIN; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TASKS_SURFACE_BODY_TOP_MARGIN; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.TOP_TOOLBAR_PLACEHOLDER_HEIGHT; |
| import static org.chromium.chrome.features.tasks.TasksSurfaceProperties.VOICE_SEARCH_BUTTON_CLICK_LISTENER; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.text.Editable; |
| import android.view.View; |
| import android.view.View.OnClickListener; |
| import android.view.ViewGroup; |
| |
| import androidx.annotation.ColorInt; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.VisibleForTesting; |
| |
| import org.chromium.base.Callback; |
| import org.chromium.base.CallbackController; |
| import org.chromium.base.ObserverList; |
| import org.chromium.base.ThreadUtils; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.base.supplier.ObservableSupplier; |
| import org.chromium.base.supplier.ObservableSupplierImpl; |
| import org.chromium.base.supplier.OneshotSupplier; |
| import org.chromium.base.supplier.Supplier; |
| import org.chromium.base.task.PostTask; |
| import org.chromium.base.task.TaskTraits; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.WarmupManager; |
| import org.chromium.chrome.browser.back_press.BackPressManager; |
| import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider; |
| import org.chromium.chrome.browser.feed.FeedActionDelegate; |
| import org.chromium.chrome.browser.feed.FeedReliabilityLogger; |
| import org.chromium.chrome.browser.flags.ChromeFeatureList; |
| import org.chromium.chrome.browser.lens.LensEntryPoint; |
| import org.chromium.chrome.browser.lens.LensMetrics; |
| import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher; |
| import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver; |
| import org.chromium.chrome.browser.logo.LogoCoordinator; |
| import org.chromium.chrome.browser.logo.LogoUtils; |
| import org.chromium.chrome.browser.logo.LogoView; |
| import org.chromium.chrome.browser.ntp.NewTabPageLaunchOrigin; |
| import org.chromium.chrome.browser.omnibox.OmniboxFocusReason; |
| import org.chromium.chrome.browser.omnibox.OmniboxStub; |
| import org.chromium.chrome.browser.omnibox.UrlFocusChangeListener; |
| import org.chromium.chrome.browser.omnibox.voice.VoiceRecognitionHandler; |
| import org.chromium.chrome.browser.preferences.ChromePreferenceKeys; |
| import org.chromium.chrome.browser.preferences.ChromeSharedPreferences; |
| import org.chromium.chrome.browser.preferences.Pref; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; |
| import org.chromium.chrome.browser.tab.Tab; |
| import org.chromium.chrome.browser.tab.TabLaunchType; |
| import org.chromium.chrome.browser.tab.TabSelectionType; |
| import org.chromium.chrome.browser.tabmodel.TabCreator; |
| import org.chromium.chrome.browser.tabmodel.TabCreatorManager; |
| 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.tabmodel.TabPersistentStore.ActiveTabState; |
| import org.chromium.chrome.browser.tasks.ReturnToChromeUtil; |
| import org.chromium.chrome.browser.tasks.tab_management.TabManagementDelegate.TabSwitcherType; |
| import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher; |
| import org.chromium.chrome.browser.tasks.tab_management.TabSwitcher.Controller; |
| import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager; |
| import org.chromium.chrome.features.start_surface.StartSurface.TabSwitcherViewObserver; |
| import org.chromium.components.browser_ui.styles.ChromeColors; |
| import org.chromium.components.browser_ui.widget.gesture.BackPressHandler; |
| import org.chromium.components.embedder_support.util.UrlUtilities; |
| import org.chromium.components.prefs.PrefService; |
| import org.chromium.content_public.browser.LoadUrlParams; |
| import org.chromium.ui.modelutil.PropertyModel; |
| import org.chromium.ui.text.EmptyTextWatcher; |
| import org.chromium.ui.util.ColorUtils; |
| |
| import java.util.List; |
| |
| /** The mediator implements the logic to interact with the surfaces and caller. */ |
| class StartSurfaceMediator |
| implements TabSwitcher.TabSwitcherViewObserver, |
| StartSurface.OnTabSelectingListener, |
| BackPressHandler, |
| LogoCoordinator.VisibilityObserver, |
| PauseResumeWithNativeObserver { |
| /** 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<TabSwitcherViewObserver> mObservers = new ObserverList<>(); |
| private TabSwitcher.Controller mController; |
| private final TabModelSelector mTabModelSelector; |
| @Nullable private final PropertyModel mPropertyModel; |
| @Nullable private final SecondaryTasksSurfaceInitializer mSecondaryTasksSurfaceInitializer; |
| private final boolean mIsStartSurfaceEnabled; |
| private final ObserverList<StartSurface.StateObserver> mStateObservers = new ObserverList<>(); |
| private final boolean mHadWarmStart; |
| private final boolean mExcludeQueryTiles; |
| private final Runnable mInitializeMVTilesRunnable; |
| private final Supplier<Tab> mParentTabSupplier; |
| private final ObservableSupplierImpl<Boolean> mBackPressChangedSupplier = |
| new ObservableSupplierImpl<>(); |
| private final CallbackController mCallbackController = new CallbackController(); |
| private final View mLogoContainerView; |
| private final boolean mIsFeedGoneImprovementEnabled; |
| private final boolean mMoveDownLogo; |
| private final ActivityLifecycleDispatcher mActivityLifecycleDispatcher; |
| private final TabCreatorManager mTabCreatorManager; |
| private final boolean mUseMagicSpace; |
| private final boolean mIsSurfacePolishEnabled; |
| |
| private boolean mShouldIgnoreTabSelecting; |
| |
| // 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"; |
| |
| private static final int LAST_SHOW_TIME_NOT_SET = -1; |
| @Nullable private ExploreSurfaceCoordinatorFactory mExploreSurfaceCoordinatorFactory; |
| @Nullable private TabSwitcher.Controller mSecondaryTasksSurfaceController; |
| @Nullable private PropertyModel mSecondaryTasksSurfacePropertyModel; |
| // Non-null when ReturnToChromeUtils#shouldImproveStartWhenFeedIsDisabled is enabled and |
| // homepage is shown. |
| @Nullable private LogoCoordinator mLogoCoordinator; |
| private boolean mIsIncognito; |
| @Nullable private OmniboxStub mOmniboxStub; |
| private Context mContext; |
| @Nullable UrlFocusChangeListener mUrlFocusChangeListener; |
| @StartSurfaceState private int mStartSurfaceState; |
| @StartSurfaceState private int mPreviousStartSurfaceState; |
| @NewTabPageLaunchOrigin private int mLaunchOrigin; |
| @Nullable private TabModel mNormalTabModel; |
| @Nullable private TabModelObserver mNormalTabModelObserver; |
| |
| @Nullable |
| // Observes both regular and incognito TabModel. This observer is responsible to initiate the |
| // hiding of the Start surface and layout. |
| private TabModelObserver mTabModelObserver; |
| |
| @Nullable private TabModelSelectorObserver mTabModelSelectorObserver; |
| private BrowserControlsStateProvider mBrowserControlsStateProvider; |
| private BrowserControlsStateProvider.Observer mBrowserControlsObserver; |
| private ActivityStateChecker mActivityStateChecker; |
| private OneshotSupplier<StartSurface> mStartSurfaceSupplier; |
| |
| // Only used when the start surface refactoring is enabled. It indicates whether the Start |
| // surface homepage is showing when we no longer calculate StartSurfaceState. |
| private boolean mIsHomepageShown; |
| |
| /** |
| * Whether a pending observer needed be added to the normal TabModel after the TabModel is |
| * initialized. |
| */ |
| private boolean mPendingObserver; |
| |
| /** |
| * 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; |
| |
| private FeedPlaceholderCoordinator mFeedPlaceholderCoordinator; |
| private boolean mHasFeedPlaceholderShown; |
| private boolean mHideOverviewOnTabSelecting = true; |
| private StartSurface.OnTabSelectingListener mOnTabSelectingListener; |
| private ViewGroup mTabSwitcherContainer; |
| // The single or carousel Tab switcher module on the Start surface. |
| // None-null when the Start surface refactoring is enabled. |
| @Nullable private TabSwitcher mTabSwitcherModule; |
| private SnackbarManager mSnackbarManager; |
| private boolean mIsNativeInitialized; |
| // The timestamp at which the Start Surface was last shown to the user. |
| private long mLastShownTimeMs = LAST_SHOW_TIME_NOT_SET; |
| private boolean mIsStartSurfaceRefactorEnabled; |
| private OnClickListener mTabSwitcherClickHandler; |
| private ObservableSupplier<Profile> mProfileSupplier; |
| |
| // TODO(crbug.com/1315676): Clean up TabSwitcher#Controller once the start surface refactoring |
| // is done. |
| StartSurfaceMediator( |
| @Nullable Controller controller, |
| ViewGroup tabSwitcherContainer, |
| @Nullable TabSwitcher tabSwitcherModule, |
| TabModelSelector tabModelSelector, |
| @Nullable PropertyModel propertyModel, |
| @Nullable SecondaryTasksSurfaceInitializer secondaryTasksSurfaceInitializer, |
| boolean isStartSurfaceEnabled, |
| Context context, |
| BrowserControlsStateProvider browserControlsStateProvider, |
| ActivityStateChecker activityStateChecker, |
| @Nullable TabCreatorManager tabCreatorManager, |
| boolean excludeQueryTiles, |
| OneshotSupplier<StartSurface> startSurfaceSupplier, |
| boolean hadWarmStart, |
| Runnable initializeMVTilesRunnable, |
| Supplier<Tab> parentTabSupplier, |
| View logoContainerView, |
| @Nullable BackPressManager backPressManager, |
| ViewGroup feedPlaceholderParentView, |
| ActivityLifecycleDispatcher activityLifecycleDispatcher, |
| OnClickListener tabSwitcherClickHandler, |
| ObservableSupplier<Profile> profileSupplier) { |
| mTabSwitcherContainer = tabSwitcherContainer; |
| mTabSwitcherModule = tabSwitcherModule; |
| mController = mTabSwitcherModule != null ? mTabSwitcherModule.getController() : controller; |
| mIsSurfacePolishEnabled = |
| isStartSurfaceEnabled && ChromeFeatureList.sSurfacePolish.isEnabled(); |
| mUseMagicSpace = isStartSurfaceEnabled && StartSurfaceConfiguration.useMagicSpace(); |
| // When a magic space is enabled on Start surface, it doesn't need a controller to handle |
| // its showing and hiding. |
| assert mController != null || mUseMagicSpace; |
| |
| mTabModelSelector = tabModelSelector; |
| mPropertyModel = propertyModel; |
| mSecondaryTasksSurfaceInitializer = secondaryTasksSurfaceInitializer; |
| mIsStartSurfaceEnabled = isStartSurfaceEnabled; |
| mContext = context; |
| mBrowserControlsStateProvider = browserControlsStateProvider; |
| mActivityStateChecker = activityStateChecker; |
| mTabCreatorManager = tabCreatorManager; |
| mExcludeQueryTiles = excludeQueryTiles; |
| mStartSurfaceSupplier = startSurfaceSupplier; |
| mHadWarmStart = hadWarmStart; |
| mLaunchOrigin = NewTabPageLaunchOrigin.UNKNOWN; |
| mInitializeMVTilesRunnable = initializeMVTilesRunnable; |
| mParentTabSupplier = parentTabSupplier; |
| mLogoContainerView = logoContainerView; |
| mActivityLifecycleDispatcher = activityLifecycleDispatcher; |
| mActivityLifecycleDispatcher.register(this); |
| // We need to check #shouldImproveStartWhenFeedIsDisabled and save it in the constructor |
| // here to keep consistent with toolbar's check. This cannot be moved to other places, since |
| // FEED_ARTICLES_LIST_VISIBLE may be changed after feed header is rendered, which then |
| // causes inconsistency with toolbar's check. |
| mIsFeedGoneImprovementEnabled = |
| ReturnToChromeUtil.shouldImproveStartWhenFeedIsDisabled(context); |
| mMoveDownLogo = ReturnToChromeUtil.moveDownLogo(); |
| mIsStartSurfaceRefactorEnabled = ReturnToChromeUtil.isStartSurfaceRefactorEnabled(context); |
| mTabSwitcherClickHandler = tabSwitcherClickHandler; |
| mProfileSupplier = profileSupplier; |
| mProfileSupplier.addObserver(this::onProfileAvailable); |
| |
| if (mPropertyModel != null) { |
| assert mIsStartSurfaceEnabled; |
| |
| if (mTabSwitcherModule != null) { |
| mPropertyModel.set(IS_TAB_CARD_VISIBLE, false); |
| } |
| |
| if (mTabSwitcherModule != null || mUseMagicSpace) { |
| // Set the initial state. |
| mPropertyModel.set(IS_SURFACE_BODY_VISIBLE, true); |
| mPropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, true); |
| mPropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE, false); |
| mPropertyModel.set(IS_LENS_BUTTON_VISIBLE, false); |
| } |
| |
| // Show feed loading image if necessary. |
| if (shouldShowFeedPlaceholder()) { |
| assert feedPlaceholderParentView != null; |
| mFeedPlaceholderCoordinator = |
| new FeedPlaceholderCoordinator(context, feedPlaceholderParentView, false); |
| mHasFeedPlaceholderShown = true; |
| } |
| |
| mIsIncognito = mTabModelSelector.isIncognitoSelected(); |
| |
| mTabModelSelectorObserver = |
| new TabModelSelectorObserver() { |
| @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); |
| updateBackgroundColor(mPropertyModel); |
| |
| if (!mUseMagicSpace) { |
| // Hide tab carousel, which does not exist in incognito mode, when closing all |
| // normal tabs. |
| // This TabModelObserver observes the regular TabModel only. |
| mNormalTabModelObserver = |
| new TabModelObserver() { |
| @Override |
| public void willCloseTab( |
| Tab tab, boolean animate, boolean didCloseAlone) { |
| if (isHomepageShown() |
| && mTabModelSelector.getModel(false).getCount() <= 1) { |
| setTabCardVisibility(false); |
| } |
| } |
| |
| @Override |
| public void tabClosureUndone(Tab tab) { |
| if (isHomepageShown()) { |
| setTabCardVisibility(true); |
| } |
| } |
| |
| @Override |
| public void restoreCompleted() { |
| if (!(mPropertyModel.get(IS_SHOWING_OVERVIEW) |
| && isHomepageShown())) { |
| return; |
| } |
| setTabCardVisibility( |
| mTabModelSelector.getModel(false).getCount() > 0 |
| && !mIsIncognito); |
| } |
| |
| @Override |
| public void willAddTab(Tab tab, @TabLaunchType int type) { |
| if (isHomepageShown() |
| && type != TabLaunchType.FROM_LONGPRESS_BACKGROUND) { |
| // Log if the creation of this tab will hide the surface and |
| // there is an ongoing feed launch. If the tab creation is due |
| // to a feed card tap, "card tapped" should already have been |
| // logged marking the end of the launch. |
| FeedReliabilityLogger logger = getFeedReliabilityLogger(); |
| if (logger != null) { |
| logger.onPageLoadStarted(); |
| } |
| } |
| |
| // When the tab model is empty and a new background tab is added, it |
| // is immediately selected, which normally causes the overview to |
| // hide. |
| // We don't want to hide the overview when creating a tab in the |
| // background, so when a background tab is added to an empty tab |
| // model, we should skip the next onTabSelecting(). |
| mHideOverviewOnTabSelecting = |
| mTabModelSelector.getModel(false).getCount() != 0 |
| || type != TabLaunchType.FROM_LONGPRESS_BACKGROUND; |
| } |
| |
| @Override |
| public void didSelectTab(Tab tab, int type, int lastId) { |
| if (mUseMagicSpace) return; |
| |
| if (type == TabSelectionType.FROM_CLOSE |
| && UrlUtilities.isNtpUrl(tab.getUrl())) { |
| setTabCardVisibility(false); |
| } |
| } |
| }; |
| } else { |
| // This TabModelObserver observes both the regular and incognito TabModels. |
| mTabModelObserver = |
| new TabModelObserver() { |
| @Override |
| public void didSelectTab(Tab tab, int type, int lastId) { |
| if (!mIsStartSurfaceRefactorEnabled |
| && mTabModelSelector.isIncognitoSelected()) { |
| return; |
| } |
| |
| assert mUseMagicSpace; |
| if (type == TabSelectionType.FROM_CLOSE |
| || type == TabSelectionType.FROM_UNDO) { |
| return; |
| } |
| onTabSelecting(mTabModelSelector.getCurrentTabId()); |
| } |
| }; |
| } |
| if (mTabModelSelector.getModels().isEmpty()) { |
| TabModelSelectorObserver selectorObserver = |
| new TabModelSelectorObserver() { |
| @Override |
| public void onChange() { |
| assert !mTabModelSelector.getModels().isEmpty(); |
| assert mTabModelSelector |
| .getTabModelFilterProvider() |
| .getTabModelFilter(false) |
| != null; |
| assert mTabModelSelector |
| .getTabModelFilterProvider() |
| .getTabModelFilter(true) |
| != null; |
| mTabModelSelector.removeObserver(this); |
| mNormalTabModel = mTabModelSelector.getModel(false); |
| if (mPendingObserver) { |
| mPendingObserver = false; |
| if (!mUseMagicSpace) { |
| mNormalTabModel.addObserver(mNormalTabModelObserver); |
| } else { |
| mTabModelSelector |
| .getTabModelFilterProvider() |
| .addTabModelFilterObserver(mTabModelObserver); |
| } |
| } |
| } |
| }; |
| mTabModelSelector.addObserver(selectorObserver); |
| } else { |
| mNormalTabModel = mTabModelSelector.getModel(false); |
| } |
| |
| mBrowserControlsObserver = |
| new BrowserControlsStateProvider.Observer() { |
| @Override |
| public void onControlsOffsetChanged( |
| int topOffset, |
| int topControlsMinHeightOffset, |
| int bottomOffset, |
| int bottomControlsMinHeightOffset, |
| boolean needsAnimate) { |
| if (isHomepageShown()) { |
| // Set the top margin to the top controls min height (indicator |
| // height if it's shown) since the toolbar height as extra margin |
| // is handled by top toolbar placeholder. |
| setTopMargin( |
| mBrowserControlsStateProvider |
| .getTopControlsMinHeightOffset()); |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { |
| // Set the top margin to the top controls offset (toolbar height + |
| // indicator height). |
| setTopMargin(mBrowserControlsStateProvider.getContentOffset()); |
| } else { |
| setTopMargin(0); |
| } |
| } |
| |
| @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 (isHomepageShown()) { |
| setBottomMargin(bottomControlsHeight); |
| } else { |
| setBottomMargin(0); |
| } |
| } |
| }; |
| |
| mUrlFocusChangeListener = |
| new UrlFocusChangeListener() { |
| @Override |
| public void onUrlFocusChange(boolean hasFocus) { |
| if (hasFakeSearchBox()) { |
| setFakeBoxVisibility(!hasFocus); |
| // TODO(crbug.com/1365694): We should call |
| // setLogoVisibility(!hasFocus) here. |
| // However, AppBarLayout's getTotalScrollRange() eliminates the gone |
| // child view's heights. Therefore, when focus is got, the |
| // AppBarLayout's scroll offset (based on getTotalScrollRange()) |
| // doesn't count the logo's height; when focus is cleared, this |
| // wrong (smaller than actual) scroll offset is restored, causing |
| // AppBarLayout to show partially. |
| // Actually setting fake box gone also causes similar offset |
| // problem, but we decided to keep fake box as-is for now for two |
| // reasons: |
| // 1. The fake box's height is small enough. Although AppBarLayout |
| // still shows partial blank bottom part when focus is cleared, |
| // it's small enough and not noticeable. |
| // 2. It would be confusing if both real and fake search boxes are |
| // visible to users. |
| // |
| // We should find a way to set both views gone when search box is |
| // focused without causing offset issues, but right now it's unclear |
| // what the plan could be regarding this stuff. |
| } |
| notifyStateChange(); |
| } |
| }; |
| |
| tweakMarginsBetweenSections(); |
| } |
| |
| if (mController != null) { |
| mController.addTabSwitcherViewObserver(this); |
| } |
| mPreviousStartSurfaceState = StartSurfaceState.NOT_SHOWN; |
| mStartSurfaceState = StartSurfaceState.NOT_SHOWN; |
| |
| if (backPressManager != null && BackPressManager.isEnabled()) { |
| backPressManager.addHandler(this, Type.START_SURFACE); |
| if (mPropertyModel != null) { |
| mPropertyModel.addObserver( |
| (source, key) -> { |
| if (key == IS_INCOGNITO) notifyBackPressStateChanged(); |
| }); |
| } |
| if (mController != null) { |
| mController |
| .getHandleBackPressChangedSupplier() |
| .addObserver((v) -> notifyBackPressStateChanged()); |
| mController |
| .isDialogVisibleSupplier() |
| .addObserver((v) -> notifyBackPressStateChanged()); |
| } |
| notifyBackPressStateChanged(); |
| } |
| } |
| |
| void initWithNative( |
| @Nullable OmniboxStub omniboxStub, |
| @Nullable ExploreSurfaceCoordinatorFactory exploreSurfaceCoordinatorFactory, |
| PrefService prefService, |
| @Nullable SnackbarManager snackbarManager) { |
| mIsNativeInitialized = true; |
| mOmniboxStub = omniboxStub; |
| mExploreSurfaceCoordinatorFactory = exploreSurfaceCoordinatorFactory; |
| mSnackbarManager = snackbarManager; |
| if (mPropertyModel != null) { |
| assert mOmniboxStub != null; |
| |
| // Initialize. |
| // Note that isVoiceSearchEnabled will return false in incognito mode. |
| mPropertyModel.set( |
| IS_VOICE_RECOGNITION_BUTTON_VISIBLE, |
| mOmniboxStub.getVoiceRecognitionHandler().isVoiceSearchEnabled()); |
| updateLensVisibility(); |
| |
| // This is for Instant Start when overview is already visible while the omnibox, Feed |
| // and MV tiles haven't been set. |
| if (isHomepageShown()) { |
| mOmniboxStub.addUrlFocusChangeListener(mUrlFocusChangeListener); |
| if (mExploreSurfaceCoordinatorFactory != null) { |
| setExploreSurfaceVisibility(!mIsIncognito); |
| } |
| if (mInitializeMVTilesRunnable != null) mInitializeMVTilesRunnable.run(); |
| if (mLogoCoordinator != null) mLogoCoordinator.initWithNative(); |
| } |
| |
| if (mTabSwitcherModule != null || mUseMagicSpace) { |
| mPropertyModel.set( |
| FAKE_SEARCH_BOX_CLICK_LISTENER, |
| v -> { |
| mOmniboxStub.setUrlBarFocus( |
| true, null, OmniboxFocusReason.TASKS_SURFACE_FAKE_BOX_TAP); |
| RecordUserAction.record("TasksSurface.FakeBox.Tapped"); |
| }); |
| mPropertyModel.set( |
| FAKE_SEARCH_BOX_TEXT_WATCHER, |
| new EmptyTextWatcher() { |
| @Override |
| public void afterTextChanged(Editable s) { |
| if (s.length() == 0) return; |
| mOmniboxStub.setUrlBarFocus( |
| true, |
| s.toString(), |
| OmniboxFocusReason.TASKS_SURFACE_FAKE_BOX_LONG_PRESS); |
| RecordUserAction.record("TasksSurface.FakeBox.LongPressed"); |
| |
| // This won't cause infinite loop since we checked s.length() == 0 |
| // above. |
| s.clear(); |
| } |
| }); |
| mPropertyModel.set( |
| VOICE_SEARCH_BUTTON_CLICK_LISTENER, |
| v -> { |
| FeedReliabilityLogger feedReliabilityLogger = |
| getFeedReliabilityLogger(); |
| if (feedReliabilityLogger != null) { |
| feedReliabilityLogger.onVoiceSearch(); |
| } |
| mOmniboxStub |
| .getVoiceRecognitionHandler() |
| .startVoiceRecognition( |
| VoiceRecognitionHandler.VoiceInteractionSource |
| .TASKS_SURFACE); |
| RecordUserAction.record("TasksSurface.FakeBox.VoiceSearch"); |
| }); |
| |
| mPropertyModel.set( |
| LENS_BUTTON_CLICK_LISTENER, |
| v -> { |
| LensMetrics.recordClicked(LensEntryPoint.TASKS_SURFACE); |
| mOmniboxStub.startLens(LensEntryPoint.TASKS_SURFACE); |
| }); |
| } |
| } |
| if (mTabSwitcherModule != null) { |
| mTabSwitcherModule.initWithNative(); |
| } |
| mFeedVisibilityPrefOnStartUp = prefService.getBoolean(Pref.ARTICLES_LIST_VISIBLE); |
| |
| // Trigger the creation of spare tab for StartSurface after the native is initialized to |
| // speed up navigation from start. |
| maybeScheduleSpareTabCreation(); |
| } |
| |
| void onProfileAvailable(Profile profile) { |
| if (profile.isOffTheRecord()) return; |
| |
| TemplateUrlServiceFactory.getForProfile(profile).addObserver(this::updateLensVisibility); |
| mProfileSupplier.removeObserver(this::onProfileAvailable); |
| } |
| |
| private void updateLensVisibility() { |
| if (mOmniboxStub == null) return; |
| |
| boolean shouldShowLensButton = mOmniboxStub.isLensEnabled(LensEntryPoint.TASKS_SURFACE); |
| LensMetrics.recordShown(LensEntryPoint.TASKS_SURFACE, shouldShowLensButton); |
| mPropertyModel.set(IS_LENS_BUTTON_VISIBLE, shouldShowLensButton); |
| } |
| |
| void destroy() { |
| if (mLogoCoordinator != null) { |
| mLogoCoordinator.destroy(); |
| mLogoCoordinator = null; |
| } |
| if (mCallbackController != null) { |
| mCallbackController.destroy(); |
| } |
| if (mProfileSupplier.hasValue()) { |
| TemplateUrlServiceFactory.getForProfile(mProfileSupplier.get()) |
| .removeObserver(this::updateLensVisibility); |
| } |
| mProfileSupplier.removeObserver(this::onProfileAvailable); |
| mayRecordHomepageSessionEnd(); |
| mActivityLifecycleDispatcher.unregister(this); |
| } |
| |
| /** Returns true if START_SURFACE_SPARE_TAB feature is enabled. */ |
| private static boolean isStartSurfaceSpareTabEnabled() { |
| return ChromeFeatureList.isEnabled(ChromeFeatureList.START_SURFACE_SPARE_TAB); |
| } |
| |
| /** |
| * Schedules creating a spare tab when native is initialized and when start surface is shown. |
| */ |
| private void maybeScheduleSpareTabCreation() { |
| // Only create spare tab when native is initialized. If start surface is shown before native |
| // is initialized, this will be invoked later. |
| if (!mIsNativeInitialized) return; |
| |
| // Only create a spare tab if tab creator exists. |
| if (mTabCreatorManager == null) return; |
| TabCreator tabCreator = mTabCreatorManager.getTabCreator(mIsIncognito); |
| // Don't create a spare tab when no tab creator is present. |
| if (tabCreator == null) return; |
| |
| // Only create a spare tab if the StartSurfaceSpareTab feature is enabled. |
| if (!isStartSurfaceSpareTabEnabled()) return; |
| |
| // Only create a spare tab when start surface is shown. |
| if (!isHomepageShown()) return; |
| |
| recordTimeBetweenShowAndCreate(); |
| |
| if (!mIsIncognito) { |
| Profile profile = mProfileSupplier.get(); |
| |
| // We use UI_DEFAULT priority to not slow down high priority tasks such as user input. |
| // As this is behavior is behind a feature flag, based on the results we will deviate to |
| // lower priority if needed. |
| PostTask.runOrPostTask( |
| TaskTraits.UI_DEFAULT, |
| () -> WarmupManager.getInstance().createRegularSpareTab(profile)); |
| } |
| } |
| |
| /** |
| * Show Start Surface home view. Note: this should be called only when refactor flag is enabled. |
| * @param animate Whether to play an entry animation. |
| */ |
| void show(boolean animate) { |
| assert ReturnToChromeUtil.isStartSurfaceEnabled(mContext) && mIsStartSurfaceRefactorEnabled; |
| |
| // This null check is for testing. |
| if (mPropertyModel == null) return; |
| |
| mIsHomepageShown = true; |
| notifyShowStateChange(); |
| |
| mIsIncognito = mTabModelSelector.isIncognitoSelected(); |
| mPropertyModel.set(IS_INCOGNITO, mIsIncognito); |
| updateBackgroundColor(mPropertyModel); |
| setMVTilesVisibility(!mIsIncognito); |
| setLogoVisibility(!mIsIncognito); |
| setTabCardVisibility(getNormalTabCount() > 0 && !mIsIncognito); |
| setExploreSurfaceVisibility(!mIsIncognito && mExploreSurfaceCoordinatorFactory != null); |
| // TODO(qinmin): show query tiles when flag is enabled. |
| setQueryTilesVisibility(false); |
| setFakeBoxVisibility(!mIsIncognito); |
| updateTopToolbarPlaceholderHeight(); |
| // Set the top margin to the top controls min height (indicator height if it's shown) |
| // since the toolbar height as extra margin is handled by top toolbar placeholder. |
| setTopMargin(mBrowserControlsStateProvider.getTopControlsMinHeight()); |
| // Only pad single pane home page since tabs grid has already been padding for the |
| // bottom bar. |
| setBottomMargin(mBrowserControlsStateProvider.getBottomControlsHeight()); |
| setIncognitoModeDescriptionVisibility( |
| mIsIncognito && (mTabModelSelector.getModel(true).getCount() <= 0)); |
| |
| // Make sure ExploreSurfaceCoordinator is built before the explore surface is showing |
| // by default. |
| if (mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE) |
| && mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR) == null |
| && !mActivityStateChecker.isFinishingOrDestroyed() |
| && mExploreSurfaceCoordinatorFactory != null) { |
| createAndSetExploreSurfaceCoordinator(); |
| } |
| |
| // TODO(crbug.com/1315676): Remove this property key since overview should always be visible |
| // when show() is called. |
| mPropertyModel.set(IS_SHOWING_OVERVIEW, true); |
| |
| if (mNormalTabModel != null) { |
| if (!mUseMagicSpace) { |
| mNormalTabModel.addObserver(mNormalTabModelObserver); |
| } else { |
| mTabModelSelector |
| .getTabModelFilterProvider() |
| .addTabModelFilterObserver(mTabModelObserver); |
| } |
| } else { |
| mPendingObserver = true; |
| } |
| |
| mTabModelSelector.addObserver(mTabModelSelectorObserver); |
| |
| if (mBrowserControlsObserver != null) { |
| mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver); |
| } |
| |
| if (mOmniboxStub != null) { |
| mOmniboxStub.addUrlFocusChangeListener(mUrlFocusChangeListener); |
| } |
| |
| // This should only be called for single or carousel tab switcher. |
| if (mController != null) { |
| mController.showTabSwitcherView(animate); |
| } |
| |
| RecordUserAction.record("StartSurface.Shown"); |
| RecordUserAction.record("StartSurface.SinglePane.Home"); |
| mayRecordHomepageSessionBegin(); |
| |
| maybeScheduleSpareTabCreation(); |
| } |
| |
| void setSecondaryTasksSurfacePropertyModel(PropertyModel propertyModel) { |
| mSecondaryTasksSurfacePropertyModel = propertyModel; |
| mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO, mIsIncognito); |
| updateBackgroundColor(mSecondaryTasksSurfacePropertyModel); |
| |
| // 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(QUERY_TILES_VISIBLE, false); |
| mSecondaryTasksSurfacePropertyModel.set(IS_VOICE_RECOGNITION_BUTTON_VISIBLE, false); |
| mSecondaryTasksSurfacePropertyModel.set(IS_LENS_BUTTON_VISIBLE, false); |
| } |
| |
| void addStateChangeObserver(StartSurface.StateObserver observer) { |
| mStateObservers.addObserver(observer); |
| } |
| |
| void removeStateChangeObserver(StartSurface.StateObserver observer) { |
| mStateObservers.removeObserver(observer); |
| } |
| |
| // Implements StartSurface.Controller |
| // TODO(crbug.com/1115757): After crrev.com/c/2315823, Overview state and Startsurface state are |
| // two different things, audit the wording usage and see if we can rename this method to |
| // setStartSurfaceState. |
| void setStartSurfaceState( |
| @StartSurfaceState int state, @NewTabPageLaunchOrigin int launchOrigin) { |
| // TODO(crbug.com/1039691): Refactor into state and trigger to separate SHOWING and SHOWN |
| // states. |
| |
| if (mPropertyModel == null || state == mStartSurfaceState) return; |
| |
| // Cache previous state. |
| int cachedPreviousState = mPreviousStartSurfaceState; |
| if (mStartSurfaceState != StartSurfaceState.NOT_SHOWN) { |
| mPreviousStartSurfaceState = mStartSurfaceState; |
| } |
| |
| mStartSurfaceState = 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) |
| && mStartSurfaceState != StartSurfaceState.NOT_SHOWN |
| && !isShownState(mStartSurfaceState)) { |
| // Compute SHOWN state before updating previous state, because the previous state is |
| // still needed to compute the shown state. |
| @StartSurfaceState int shownState = computeOverviewStateShown(); |
| |
| mStartSurfaceState = shownState; |
| setOverviewStateInternal(); |
| } |
| notifyStateChange(); |
| |
| setLaunchOrigin(launchOrigin); |
| // Metrics collection |
| if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) { |
| RecordUserAction.record("StartSurface.SinglePane.Home"); |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { |
| RecordUserAction.record("StartSurface.SinglePane.Tabswitcher"); |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWING_PREVIOUS |
| && cachedPreviousState == StartSurfaceState.SHOWN_HOMEPAGE) { |
| ReturnToChromeUtil.recordBackNavigationToStart("FromTab"); |
| } |
| } |
| |
| void setStartSurfaceState(@StartSurfaceState int state) { |
| setStartSurfaceState(state, mLaunchOrigin); |
| } |
| |
| void setLaunchOrigin(@NewTabPageLaunchOrigin int launchOrigin) { |
| if (launchOrigin == NewTabPageLaunchOrigin.WEB_FEED) { |
| StartSurfaceUserData.getInstance().saveFeedInstanceState(null); |
| } |
| mLaunchOrigin = launchOrigin; |
| // If the ExploreSurfaceCoordinator is already initialized, set the TabId. |
| if (mPropertyModel == null) return; |
| ExploreSurfaceCoordinator exploreSurfaceCoordinator = |
| mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR); |
| if (exploreSurfaceCoordinator != null) { |
| exploreSurfaceCoordinator.setTabIdFromLaunchOrigin(mLaunchOrigin); |
| } |
| } |
| |
| void resetScrollPosition() { |
| if (mPropertyModel == null) return; |
| |
| mPropertyModel.set(RESET_TASK_SURFACE_HEADER_SCROLL_POSITION, true); |
| mPropertyModel.set(RESET_FEED_SURFACE_SCROLL_POSITION, true); |
| StartSurfaceUserData.getInstance().saveFeedInstanceState(null); |
| } |
| |
| // TODO(crbug.com/1115757): After crrev.com/c/2315823, Overview state and Startsurface state are |
| // two different things, audit the wording usage and see if we can rename this method to |
| // setStartSurfaceStateInternal. |
| private void setOverviewStateInternal() { |
| if (mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE |
| || mStartSurfaceState == StartSurfaceState.SHOWING_START) { |
| // When entering the Start surface by tapping home button or new tab page, we need to |
| // reset the scrolling position. |
| resetScrollPosition(); |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWING_TABSWITCHER) { |
| onHide(); |
| // Set secondary surface visible to make sure tab list recyclerview is updated in time |
| // (before GTS animations start). We need to skip |
| // mSecondaryTasksSurfaceController#showOverview here since it will hide GTS animations. |
| setSecondaryTasksSurfaceVisibility( |
| /* isVisible= */ true, /* skipUpdateController= */ true); |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) { |
| if (mPreviousStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { |
| mayRecordHomepageSessionBegin(); |
| } |
| boolean hasNormalTab = getNormalTabCount() > 0; |
| |
| // If new home surface for home button is enabled, MV tiles and carousel tab switcher |
| // will not show. |
| setMVTilesVisibility(!mIsIncognito); |
| setLogoVisibility(!mIsIncognito); |
| setTabCardVisibility(hasNormalTab && !mIsIncognito); |
| setExploreSurfaceVisibility(!mIsIncognito && mExploreSurfaceCoordinatorFactory != null); |
| setQueryTilesVisibility(!mIsIncognito); |
| setFakeBoxVisibility(!mIsIncognito); |
| setSecondaryTasksSurfaceVisibility(mIsIncognito, /* skipUpdateController= */ false); |
| updateTopToolbarPlaceholderHeight(); |
| // Set the top margin to the top controls min height (indicator height if it's shown) |
| // since the toolbar height as extra margin is handled by top toolbar placeholder. |
| setTopMargin(mBrowserControlsStateProvider.getTopControlsMinHeight()); |
| // Only pad single pane home page since tabs grid has already been padding for the |
| // bottom bar. |
| setBottomMargin(mBrowserControlsStateProvider.getBottomControlsHeight()); |
| if (mNormalTabModel != null) { |
| mNormalTabModel.addObserver(mNormalTabModelObserver); |
| } else { |
| mPendingObserver = true; |
| } |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { |
| if (mPreviousStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) { |
| mayRecordHomepageSessionEnd(); |
| } |
| setTabCardVisibility(false); |
| setMVTilesVisibility(false); |
| setLogoVisibility(false); |
| setQueryTilesVisibility(false); |
| setFakeBoxVisibility(false); |
| setSecondaryTasksSurfaceVisibility( |
| /* isVisible= */ true, /* skipUpdateController= */ false); |
| setExploreSurfaceVisibility(false); |
| updateTopToolbarPlaceholderHeight(); |
| // Set the top margin to the top controls height (toolbar height + indicator height). |
| setTopMargin(mBrowserControlsStateProvider.getTopControlsHeight()); |
| setBottomMargin(0); |
| } else if (mStartSurfaceState == StartSurfaceState.NOT_SHOWN) { |
| if (mSecondaryTasksSurfacePropertyModel != null) { |
| setSecondaryTasksSurfaceVisibility( |
| /* isVisible= */ false, /* skipUpdateController= */ false); |
| } |
| } |
| |
| if (isShownState(mStartSurfaceState)) { |
| setIncognitoModeDescriptionVisibility( |
| mIsIncognito && (mTabModelSelector.getModel(true).getCount() <= 0)); |
| } |
| } |
| |
| @StartSurfaceState |
| int getStartSurfaceState() { |
| return mStartSurfaceState; |
| } |
| |
| int getPreviousStartSurfaceState() { |
| return mPreviousStartSurfaceState; |
| } |
| |
| ViewGroup getTabSwitcherContainer() { |
| return mTabSwitcherContainer; |
| } |
| |
| @Nullable |
| TabSwitcher.Controller getTabSwitcherController() { |
| return mSecondaryTasksSurfaceController; |
| } |
| |
| void setSnackbarParentView(ViewGroup parentView) { |
| if (mSnackbarManager == null) return; |
| mSnackbarManager.setParentView(parentView); |
| } |
| |
| void addTabSwitcherViewObserver(TabSwitcherViewObserver observer) { |
| mObservers.addObserver(observer); |
| } |
| |
| void removeTabSwitcherViewObserver(TabSwitcherViewObserver observer) { |
| mObservers.removeObserver(observer); |
| } |
| |
| void hideTabSwitcherView(boolean animate) { |
| if (mController != null) { |
| mController.hideTabSwitcherView(animate); |
| } else { |
| startedHiding(); |
| } |
| } |
| |
| void beforeHideTabSwitcherView() { |
| if (mController != null) { |
| mController.prepareHideTabSwitcherView(); |
| } |
| } |
| |
| 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); |
| updateBackgroundColor(mPropertyModel); |
| |
| // if OverviewModeState is NOT_SHOWN, default to SHOWING_TABSWITCHER. This should only |
| // happen when entering Start through SwipeDown gesture on URL bar. |
| if (mStartSurfaceState == StartSurfaceState.NOT_SHOWN) { |
| mStartSurfaceState = StartSurfaceState.SHOWING_TABSWITCHER; |
| } |
| |
| // set OverviewModeState |
| @StartSurfaceState int shownState = computeOverviewStateShown(); |
| assert (isShownState(shownState)); |
| setStartSurfaceState(shownState); |
| |
| // Make sure ExploreSurfaceCoordinator is built before the explore surface is showing |
| // by default. |
| if (mPropertyModel.get(IS_EXPLORE_SURFACE_VISIBLE) |
| && mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR) == null |
| && !mActivityStateChecker.isFinishingOrDestroyed() |
| && mExploreSurfaceCoordinatorFactory != null) { |
| createAndSetExploreSurfaceCoordinator(); |
| } |
| mTabModelSelector.addObserver(mTabModelSelectorObserver); |
| |
| if (mBrowserControlsObserver != null) { |
| mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver); |
| } |
| |
| mPropertyModel.set(IS_SHOWING_OVERVIEW, true); |
| if (mOmniboxStub != null) { |
| mOmniboxStub.addUrlFocusChangeListener(mUrlFocusChangeListener); |
| } |
| } |
| mayRecordHomepageSessionBegin(); |
| if (mController != null) { |
| mController.showTabSwitcherView(animate); |
| } |
| |
| maybeScheduleSpareTabCreation(); |
| } |
| |
| /** |
| * This function no longer handles the case when Start is disabled. Instead, the back operations |
| * of the grid tab switcher is handled by TabSwitcherMediator. |
| */ |
| boolean onBackPressed() { |
| boolean ret = onBackPressedInternal(); |
| if (ret) { |
| BackPressManager.record(BackPressHandler.Type.START_SURFACE); |
| } |
| return ret; |
| } |
| |
| /** |
| * This function handles the following cases: |
| * 1) Start surface is showing, including with/without refactoring enabled; |
| * 2) Grid tab switcher is showing, but only when Start surface is enabled and refactoring is |
| * disabled. This is because the transitions between Start surface and tab switcher |
| * (secondary tasks view) is handled by the same Layout via state changes. So we have to |
| * handle the two surfaces together. |
| * In the ideal scenarios: when a) Start is disabled and b) Start surface refactoring is |
| * enabled, the back operations of the grid tab switcher is handled by TabSwitcherMediator. |
| */ |
| private boolean onBackPressedInternal() { |
| boolean isOnHomepage = isHomepageShown(); |
| |
| // When the SecondaryTasksSurface is shown, the TabGridDialog is controlled by |
| // mSecondaryTasksSurfaceController, while the TabListEditor dialog is controlled |
| // by mController. Therefore, we need to check both controllers whether any dialog is |
| // visible. If so, the corresponding controller will handle the back button. |
| // When the Start surface is shown, tapping "Group Tabs" from menu will also show the |
| // the TabListEditor dialog. Therefore, we need to check both controllers as well. |
| if (mSecondaryTasksSurfaceController != null |
| && mSecondaryTasksSurfaceController.isDialogVisible()) { |
| boolean ret = mSecondaryTasksSurfaceController.onBackPressed(); |
| assert !BackPressManager.isEnabled() || ret |
| : String.format( |
| "Wrong back press state: %s, start surface: %s", |
| mSecondaryTasksSurfaceController.getClass().getName(), |
| mStartSurfaceState); |
| return ret; |
| } else if (mController != null && mController.isDialogVisible()) { |
| boolean ret = mController.onBackPressed(); |
| assert !BackPressManager.isEnabled() || ret |
| : String.format( |
| "Wrong back press state: %s, start surface: %s", |
| mController.getClass().getName(), mStartSurfaceState); |
| return ret; |
| } |
| |
| if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { |
| if (mPreviousStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE && !mIsIncognito) { |
| // Secondary tasks surface is used as the main surface in incognito mode. |
| // If we reached Tab switcher from HomePage, and there isn't any dialog shown, |
| // updates the state, and ChromeTabbedActivity will handle the back button. |
| setStartSurfaceState(StartSurfaceState.SHOWN_HOMEPAGE); |
| ReturnToChromeUtil.recordBackNavigationToStart("FromTabSwitcher"); |
| return true; |
| } else { |
| boolean ret = mSecondaryTasksSurfaceController.onBackPressed(); |
| assert !BackPressManager.isEnabled() || ret |
| : String.format( |
| "Wrong back press state: %s, start surface: %s", |
| mSecondaryTasksSurfaceController.getClass().getName(), |
| mStartSurfaceState); |
| return ret; |
| } |
| } |
| |
| if (isOnHomepage) { |
| FeedReliabilityLogger feedReliabilityLogger = getFeedReliabilityLogger(); |
| if (feedReliabilityLogger != null) { |
| feedReliabilityLogger.onNavigateBack(); |
| } |
| } |
| |
| if (mController != null) { |
| // crbug.com/1420410: secondary task surface might be doing animations when transiting |
| // to/from tab switcher and then intercept back press to wait for animation to be |
| // finished. |
| boolean ret = |
| mController.onBackPressed() |
| || (mSecondaryTasksSurfaceController != null |
| && mSecondaryTasksSurfaceController.onBackPressed()); |
| assert !BackPressManager.isEnabled() || ret |
| : String.format( |
| "Wrong back press state: %s, start surface: %s", |
| mController.getClass().getName(), mStartSurfaceState); |
| return ret; |
| } |
| return false; |
| } |
| |
| void onHide() { |
| if (mFeedPlaceholderCoordinator != null) { |
| mFeedPlaceholderCoordinator.destroy(); |
| mFeedPlaceholderCoordinator = null; |
| } |
| if (mTabSwitcherModule != null) { |
| mTabSwitcherModule.getTabListDelegate().postHiding(); |
| } |
| } |
| |
| @Override |
| public @BackPressResult int handleBackPress() { |
| boolean ret = onBackPressedInternal(); |
| notifyBackPressStateChanged(); |
| return ret ? BackPressResult.SUCCESS : BackPressResult.FAILURE; |
| } |
| |
| @Override |
| public ObservableSupplier<Boolean> getHandleBackPressChangedSupplier() { |
| return mBackPressChangedSupplier; |
| } |
| |
| void onOverviewShownAtLaunch(long activityCreationTimeMs) { |
| if (mController != null) { |
| mController.onOverviewShownAtLaunch(activityCreationTimeMs); |
| } |
| if (mPropertyModel != null) { |
| ExploreSurfaceCoordinator exploreSurfaceCoordinator = |
| mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR); |
| if (exploreSurfaceCoordinator != null) { |
| exploreSurfaceCoordinator.onOverviewShownAtLaunch(activityCreationTimeMs); |
| } |
| } |
| |
| assert mPropertyModel == null || mFeedVisibilityInSharedPreferenceOnStartUp != null; |
| if (mFeedVisibilityPrefOnStartUp != null) { |
| RecordHistogram.recordBooleanHistogram( |
| FEED_VISIBILITY_CONSISTENCY, |
| mFeedVisibilityPrefOnStartUp.equals( |
| mFeedVisibilityInSharedPreferenceOnStartUp)); |
| } |
| if (mFeedPlaceholderCoordinator != null) { |
| mFeedPlaceholderCoordinator.onOverviewShownAtLaunch(activityCreationTimeMs); |
| } |
| } |
| |
| @Deprecated |
| // TODO(1347089): Removes this test after the refactoring is enabled by default. This is because |
| // the StartSurfaceState will go away. |
| boolean isShowingStartSurfaceHomepage() { |
| // When state is SHOWN_HOMEPAGE or SHOWING_HOMEPAGE or SHOWING_START, state surface homepage |
| // is showing. When state is StartSurfaceState.SHOWING_PREVIOUS and the previous state is |
| // SHOWN_HOMEPAGE or NOT_SHOWN, homepage is showing. |
| return mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE |
| || mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE |
| || mStartSurfaceState == StartSurfaceState.SHOWING_START |
| || (mStartSurfaceState == StartSurfaceState.SHOWING_PREVIOUS |
| && (mPreviousStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE |
| || mPreviousStartSurfaceState == StartSurfaceState.NOT_SHOWN)); |
| } |
| |
| // Implements TabSwitcher.TabSwitcherViewObserver. |
| @Override |
| public void startedShowing() { |
| for (TabSwitcherViewObserver observer : mObservers) { |
| observer.startedShowing(); |
| } |
| } |
| |
| @Override |
| public void finishedShowing() { |
| for (TabSwitcherViewObserver observer : mObservers) { |
| observer.finishedShowing(); |
| } |
| } |
| |
| @Override |
| public void startedHiding() { |
| if (mPropertyModel != null) { |
| if (mOmniboxStub != null) { |
| mOmniboxStub.removeUrlFocusChangeListener(mUrlFocusChangeListener); |
| } |
| mPropertyModel.set(IS_SHOWING_OVERVIEW, false); |
| |
| destroyExploreSurfaceCoordinator(); |
| if (mNormalTabModel != null) { |
| if (mNormalTabModelObserver != null) { |
| mNormalTabModel.removeObserver(mNormalTabModelObserver); |
| } else if (mTabModelObserver != null) { |
| mTabModelSelector |
| .getTabModelFilterProvider() |
| .removeTabModelFilterObserver(mTabModelObserver); |
| } |
| } else if (mPendingObserver) { |
| mPendingObserver = false; |
| } |
| if (mTabModelSelectorObserver != null) { |
| mTabModelSelector.removeObserver(mTabModelSelectorObserver); |
| } |
| if (mBrowserControlsObserver != null) { |
| mBrowserControlsStateProvider.removeObserver(mBrowserControlsObserver); |
| } |
| setStartSurfaceState(StartSurfaceState.NOT_SHOWN); |
| RecordUserAction.record("StartSurface.Hidden"); |
| mIsHomepageShown = false; |
| } |
| |
| // Since the start surface is hidden, destroy any spare tabs created. |
| PostTask.runOrPostTask( |
| TaskTraits.UI_DEFAULT, () -> WarmupManager.getInstance().destroySpareTab()); |
| |
| for (TabSwitcherViewObserver observer : mObservers) { |
| observer.startedHiding(); |
| } |
| } |
| |
| @Override |
| public void finishedHiding() { |
| for (TabSwitcherViewObserver observer : mObservers) { |
| observer.finishedHiding(); |
| } |
| } |
| |
| private void destroyExploreSurfaceCoordinator() { |
| ExploreSurfaceCoordinator exploreSurfaceCoordinator = |
| mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR); |
| FeedReliabilityLogger logger = getFeedReliabilityLogger(); |
| if (logger != null) { |
| mOmniboxStub.removeUrlFocusChangeListener(logger); |
| } |
| if (exploreSurfaceCoordinator != null) exploreSurfaceCoordinator.destroy(); |
| mPropertyModel.set(EXPLORE_SURFACE_COORDINATOR, null); |
| } |
| |
| // StartSurface.OnTabSelectingListener |
| @Override |
| public void onTabSelecting(int tabId) { |
| if (!mHideOverviewOnTabSelecting) { |
| mHideOverviewOnTabSelecting = true; |
| return; |
| } |
| // Because there are multiple upstream tab selection listeners that attempt to re-trigger |
| // the onTabSelecting in response to TabModelObserver#didSelectTab it is necessary to treat |
| // this as a non-rentrant method until the original operation finishes. |
| // TODO(crbug/1495121): This can be removed once StartSurfaceRefactor is cleaned up. |
| if (mShouldIgnoreTabSelecting) return; |
| mShouldIgnoreTabSelecting = true; |
| |
| assert mOnTabSelectingListener != null; |
| mOnTabSelectingListener.onTabSelecting(tabId); |
| mShouldIgnoreTabSelecting = false; |
| } |
| |
| // LogoCoordinator.VisibilityObserver |
| @Override |
| public void onLogoVisibilityChanged() { |
| updateTopToolbarPlaceholderHeight(); |
| } |
| |
| @VisibleForTesting |
| public boolean shouldShowFeedPlaceholder() { |
| if (mFeedVisibilityInSharedPreferenceOnStartUp == null) { |
| mFeedVisibilityInSharedPreferenceOnStartUp = |
| ReturnToChromeUtil.getFeedArticlesVisibility(); |
| } |
| |
| return mIsStartSurfaceEnabled |
| && ChromeFeatureList.sInstantStart.isEnabled() |
| && ReturnToChromeUtil.getFeedArticlesVisibility() |
| && !mHadWarmStart |
| && !mHasFeedPlaceholderShown; |
| } |
| |
| void setSecondaryTasksSurfaceController( |
| TabSwitcher.Controller secondaryTasksSurfaceController) { |
| mSecondaryTasksSurfaceController = secondaryTasksSurfaceController; |
| mSecondaryTasksSurfaceController |
| .isDialogVisibleSupplier() |
| .addObserver((v) -> notifyBackPressStateChanged()); |
| mSecondaryTasksSurfaceController |
| .getHandleBackPressChangedSupplier() |
| .addObserver((v) -> notifyBackPressStateChanged()); |
| } |
| |
| /** 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(EXPLORE_SURFACE_COORDINATOR) == null |
| && !mActivityStateChecker.isFinishingOrDestroyed()) { |
| createAndSetExploreSurfaceCoordinator(); |
| } |
| |
| mPropertyModel.set(IS_EXPLORE_SURFACE_VISIBLE, isVisible); |
| |
| // Pull-to-refresh is not supported when explore surface is not visible, i.e. in tab |
| // switcher mode. |
| ExploreSurfaceCoordinator exploreSurfaceCoordinator = |
| mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR); |
| if (exploreSurfaceCoordinator != null) { |
| exploreSurfaceCoordinator.enableSwipeRefresh(isVisible); |
| } |
| } |
| |
| private void updateIncognitoMode(boolean isIncognito) { |
| if (isIncognito == mIsIncognito) return; |
| mIsIncognito = isIncognito; |
| |
| mPropertyModel.set(IS_INCOGNITO, mIsIncognito); |
| updateBackgroundColor(mPropertyModel); |
| 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(); |
| } |
| |
| /** |
| * Set the visibility of secondary tasks surface. Secondary tasks surface is used for showing |
| * normal grid tab switcher and incognito gird tab switcher. |
| * @param isVisible Whether secondary tasks surface is visible. |
| * @param skipUpdateController Whether to skip mSecondaryTasksSurfaceController#showOverview and |
| * mSecondaryTasksSurfaceController#hideOverview. |
| */ |
| private void setSecondaryTasksSurfaceVisibility( |
| boolean isVisible, boolean skipUpdateController) { |
| assert mIsStartSurfaceEnabled; |
| |
| if (isVisible) { |
| if (mSecondaryTasksSurfacePropertyModel == null) { |
| setSecondaryTasksSurfaceController(mSecondaryTasksSurfaceInitializer.initialize()); |
| } |
| if (mSecondaryTasksSurfacePropertyModel != null) { |
| mSecondaryTasksSurfacePropertyModel.set(IS_FAKE_SEARCH_BOX_VISIBLE, false); |
| mSecondaryTasksSurfacePropertyModel.set(IS_INCOGNITO, mIsIncognito); |
| updateBackgroundColor(mSecondaryTasksSurfacePropertyModel); |
| } |
| if (mSecondaryTasksSurfaceController != null && !skipUpdateController) { |
| // Ensure the layout change listener and tabs are properly registered before |
| // showing. |
| if (mStartSurfaceSupplier.get() != null) { |
| mStartSurfaceSupplier.get().getGridTabListDelegate().prepareTabGridView(); |
| } |
| mSecondaryTasksSurfaceController.showTabSwitcherView(/* animate= */ true); |
| } |
| } else { |
| if (mSecondaryTasksSurfaceController != null && !skipUpdateController) { |
| mSecondaryTasksSurfaceController.hideTabSwitcherView(/* animate= */ false); |
| if (mStartSurfaceSupplier.get() != null |
| && mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE) { |
| mStartSurfaceSupplier.get().getGridTabListDelegate().postHiding(); |
| } |
| } |
| } |
| mPropertyModel.set(IS_SECONDARY_SURFACE_VISIBLE, isVisible); |
| } |
| |
| private void notifyStateChange() { |
| notifyShowStateChange(); |
| if (!mIsStartSurfaceRefactorEnabled) { |
| notifyStartSurfaceStateChange(); |
| } |
| } |
| |
| private void notifyShowStateChange() { |
| // StartSurface is being supplied with OneShotSupplier, notification sends after |
| // StartSurface is available to avoid missing events. More detail see: |
| // https://crrev.com/c/2427428. |
| if (mController != null) { |
| mController.onHomepageChanged(); |
| } |
| notifyBackPressStateChanged(); |
| } |
| |
| // TODO(1315676): Remove this when the Start surface refactoring is enabled by default. |
| private void notifyStartSurfaceStateChange() { |
| if (mSecondaryTasksSurfaceController != null) { |
| mSecondaryTasksSurfaceController.onHomepageChanged(); |
| } |
| mStartSurfaceSupplier.onAvailable( |
| (unused) -> { |
| for (StartSurface.StateObserver observer : mStateObservers) { |
| observer.onStateChanged(mStartSurfaceState, shouldShowTabSwitcherToolbar()); |
| } |
| }); |
| } |
| |
| private boolean hasFakeSearchBox() { |
| return isHomepageShown(); |
| } |
| |
| @VisibleForTesting |
| public boolean shouldShowTabSwitcherToolbar() { |
| // Always show in TABSWITCHER |
| if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER |
| || mPropertyModel.get(IS_SECONDARY_SURFACE_VISIBLE)) { |
| return true; |
| } |
| |
| // Hide when focusing the Omnibox on the primary surface. |
| return hasFakeSearchBox() && mPropertyModel.get(IS_FAKE_SEARCH_BOX_VISIBLE); |
| } |
| |
| private void setTopMargin(int topMargin) { |
| mPropertyModel.set(TOP_MARGIN, topMargin); |
| } |
| |
| private void setBottomMargin(int bottomMargin) { |
| mPropertyModel.set(BOTTOM_BAR_HEIGHT, bottomMargin); |
| } |
| |
| /** |
| * This method should be called after setLogoVisibility() since we need to know whether logo is |
| * shown or not to decide the height. |
| * @param height The height of the top toolbar placeholder. |
| */ |
| private void updateTopToolbarPlaceholderHeight() { |
| mPropertyModel.set( |
| TOP_TOOLBAR_PLACEHOLDER_HEIGHT, |
| mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER |
| ? 0 |
| : getTopToolbarPlaceholderHeight()); |
| } |
| |
| private void setTabCardVisibility(boolean isVisible) { |
| if (mUseMagicSpace) return; |
| |
| // If the single tab switcher is shown and the current selected tab is a new tab page, we |
| // shouldn't show the tab switcher layout on Start. |
| boolean shouldShowTabCard = |
| isVisible && !(isSingleTabSwitcher() && isCurrentSelectedTabNtp()); |
| |
| if (shouldShowTabCard == mPropertyModel.get(IS_TAB_CARD_VISIBLE)) return; |
| |
| mPropertyModel.set(IS_TAB_CARD_VISIBLE, shouldShowTabCard); |
| } |
| |
| private void setMVTilesVisibility(boolean isVisible) { |
| if (mInitializeMVTilesRunnable == null) return; |
| mPropertyModel.set(MV_TILES_VISIBLE, isVisible); |
| if (isVisible && mInitializeMVTilesRunnable != null) mInitializeMVTilesRunnable.run(); |
| } |
| |
| private void setLogoVisibility(boolean isVisible) { |
| if (!mIsFeedGoneImprovementEnabled && !mMoveDownLogo) return; |
| |
| if (isVisible && mLogoCoordinator == null) { |
| mLogoCoordinator = initializeLogo(); |
| if (mIsNativeInitialized) mLogoCoordinator.initWithNative(); |
| } |
| if (mLogoCoordinator != null) { |
| boolean isShowingHomepage = isHomepageShown(); |
| mLogoCoordinator.updateVisibilityAndMaybeCleanUp( |
| isShowingHomepage && isVisible, !isShowingHomepage, false); |
| } |
| } |
| |
| private void setQueryTilesVisibility(boolean isVisible) { |
| if (mExcludeQueryTiles || isVisible == mPropertyModel.get(QUERY_TILES_VISIBLE)) return; |
| mPropertyModel.set(QUERY_TILES_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 (mOmniboxStub != null) { |
| if (mOmniboxStub.getVoiceRecognitionHandler() != null) { |
| mPropertyModel.set( |
| IS_VOICE_RECOGNITION_BUTTON_VISIBLE, |
| mOmniboxStub |
| .getVoiceRecognitionHandler() |
| .isVoiceSearchEnabled()); |
| } |
| mPropertyModel.set( |
| IS_LENS_BUTTON_VISIBLE, |
| mOmniboxStub.isLensEnabled(LensEntryPoint.TASKS_SURFACE)); |
| } |
| }); |
| } |
| |
| 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); |
| } |
| } |
| |
| // TODO(crbug.com/1115757): After crrev.com/c/2315823, Overview state and Startsurface state are |
| // two different things, audit the wording usage and see if we can rename this method to |
| // computeStartSurfaceState. |
| private @StartSurfaceState int computeOverviewStateShown() { |
| if (mIsStartSurfaceEnabled) { |
| if (mStartSurfaceState == StartSurfaceState.SHOWING_PREVIOUS) { |
| assert mPreviousStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE |
| || mPreviousStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER |
| || mPreviousStartSurfaceState == StartSurfaceState.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 mPreviousStartSurfaceState == StartSurfaceState.NOT_SHOWN |
| ? StartSurfaceState.SHOWN_HOMEPAGE |
| : mPreviousStartSurfaceState; |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWING_START) { |
| if (mTabModelSelector.isIncognitoSelected()) { |
| return StartSurfaceState.SHOWN_TABSWITCHER; |
| } |
| return StartSurfaceState.SHOWN_HOMEPAGE; |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWING_TABSWITCHER) { |
| return StartSurfaceState.SHOWN_TABSWITCHER; |
| } else if (mStartSurfaceState == StartSurfaceState.SHOWING_HOMEPAGE) { |
| return StartSurfaceState.SHOWN_HOMEPAGE; |
| } else { |
| assert (isShownState(mStartSurfaceState) |
| || mStartSurfaceState == StartSurfaceState.NOT_SHOWN); |
| return mStartSurfaceState; |
| } |
| } |
| return StartSurfaceState.DISABLED; |
| } |
| |
| private boolean isShownState(@StartSurfaceState int state) { |
| return state == StartSurfaceState.SHOWN_HOMEPAGE |
| || state == StartSurfaceState.SHOWN_TABSWITCHER; |
| } |
| |
| private int getNormalTabCount() { |
| if (!mTabModelSelector.isTabStateInitialized()) { |
| return ChromeSharedPreferences.getInstance() |
| .readInt(ChromePreferenceKeys.REGULAR_TAB_COUNT); |
| } else { |
| return mTabModelSelector.getModel(false).getCount(); |
| } |
| } |
| |
| private boolean isCurrentSelectedTabNtp() { |
| Tab currentTab = mTabModelSelector.getCurrentTab(); |
| return mTabModelSelector.isTabStateInitialized() |
| && currentTab != null |
| && currentTab.getUrl() != null |
| ? UrlUtilities.isNtpUrl(currentTab.getUrl()) |
| : ChromeSharedPreferences.getInstance() |
| .readInt( |
| ChromePreferenceKeys.APP_LAUNCH_LAST_KNOWN_ACTIVE_TAB_STATE) |
| == ActiveTabState.NTP; |
| } |
| |
| private boolean isSingleTabSwitcher() { |
| return mController != null && mController.getTabSwitcherType() == TabSwitcherType.SINGLE; |
| } |
| |
| private void notifyBackPressStateChanged() { |
| mBackPressChangedSupplier.set(shouldInterceptBackPress()); |
| } |
| |
| @VisibleForTesting |
| boolean shouldInterceptBackPress() { |
| if (mSecondaryTasksSurfaceController != null |
| && mSecondaryTasksSurfaceController.isDialogVisible()) { |
| return true; |
| } else if (mController != null && mController.isDialogVisible()) { |
| return true; |
| } |
| |
| if (mStartSurfaceState == StartSurfaceState.SHOWN_TABSWITCHER) { |
| if (mPreviousStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE && !mIsIncognito) { |
| return true; |
| } else if (mSecondaryTasksSurfaceController != null) { |
| return Boolean.TRUE.equals( |
| mSecondaryTasksSurfaceController.getHandleBackPressChangedSupplier().get()); |
| } |
| } |
| |
| if (mIsStartSurfaceRefactorEnabled && ReturnToChromeUtil.isStartSurfaceEnabled(mContext)) { |
| return false; |
| } |
| |
| if (mController != null |
| && Boolean.TRUE.equals(mController.getHandleBackPressChangedSupplier().get())) { |
| return true; |
| } |
| |
| return mSecondaryTasksSurfaceController != null |
| && Boolean.TRUE.equals( |
| mSecondaryTasksSurfaceController.getHandleBackPressChangedSupplier().get()); |
| } |
| |
| boolean isLogoVisible() { |
| return mLogoCoordinator != null && mLogoCoordinator.isLogoVisible(); |
| } |
| |
| void performSearchQuery(String queryText, List<String> searchParams) { |
| if (mOmniboxStub != null) { |
| mOmniboxStub.performSearchQuery(queryText, searchParams); |
| } |
| } |
| |
| void setOnTabSelectingListener(StartSurface.OnTabSelectingListener onTabSelectingListener) { |
| mOnTabSelectingListener = onTabSelectingListener; |
| } |
| |
| int getTopToolbarPlaceholderHeight() { |
| // If logo is visible in Start surface instead of in the toolbar, we don't need to show the |
| // top margin of the fake search box. |
| return getPixelSize(R.dimen.control_container_height) |
| + (isLogoVisible() |
| ? 0 |
| : getPixelSize(R.dimen.start_surface_fake_search_box_top_margin)); |
| } |
| |
| private int getPixelSize(int id) { |
| return mContext.getResources().getDimensionPixelSize(id); |
| } |
| |
| private void createAndSetExploreSurfaceCoordinator() { |
| ExploreSurfaceCoordinator exploreSurfaceCoordinator = |
| mExploreSurfaceCoordinatorFactory.create( |
| ColorUtils.inNightMode(mContext), mHasFeedPlaceholderShown, mLaunchOrigin); |
| mPropertyModel.set(EXPLORE_SURFACE_COORDINATOR, exploreSurfaceCoordinator); |
| FeedReliabilityLogger feedReliabilityLogger = |
| exploreSurfaceCoordinator.getFeedReliabilityLogger(); |
| if (feedReliabilityLogger != null) { |
| mOmniboxStub.addUrlFocusChangeListener(feedReliabilityLogger); |
| } |
| } |
| |
| private LogoCoordinator initializeLogo() { |
| Callback<LoadUrlParams> logoClickedCallback = |
| mCallbackController.makeCancelable( |
| (urlParams) -> { |
| // On NTP, the logo is in the new tab page layout instead of the toolbar |
| // and the logo click events are processed in NewTabPageLayout. This |
| // callback passed into TopToolbarCoordinator will only be used for |
| // StartSurfaceToolbar, so add an assertion here. |
| assert ReturnToChromeUtil.isStartSurfaceEnabled(mContext); |
| ReturnToChromeUtil.handleLoadUrlFromStartSurface( |
| urlParams, /* incognito= */ false, mParentTabSupplier.get()); |
| }); |
| mLogoContainerView.setVisibility(View.VISIBLE); |
| LogoView logoView = mLogoContainerView.findViewById(R.id.search_provider_logo); |
| if (mIsSurfacePolishEnabled) { |
| LogoUtils.setLogoViewLayoutParams( |
| logoView, |
| mContext.getResources(), |
| false, |
| StartSurfaceConfiguration.SURFACE_POLISH_LESS_BRAND_SPACE.getValue()); |
| } |
| |
| mLogoCoordinator = |
| new LogoCoordinator( |
| mContext, |
| logoClickedCallback, |
| logoView, |
| true, |
| null, |
| null, |
| isHomepageShown(), |
| this); |
| return mLogoCoordinator; |
| } |
| |
| FeedReliabilityLogger getFeedReliabilityLogger() { |
| if (mPropertyModel == null) return null; |
| ExploreSurfaceCoordinator coordinator = mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR); |
| return coordinator != null ? coordinator.getFeedReliabilityLogger() : null; |
| } |
| |
| private void tweakMarginsBetweenSections() { |
| Resources resources = mContext.getResources(); |
| if (!mIsSurfacePolishEnabled) { |
| mPropertyModel.set( |
| TASKS_SURFACE_BODY_TOP_MARGIN, |
| resources.getDimensionPixelSize(R.dimen.tasks_surface_body_top_margin)); |
| } |
| |
| if (mIsSurfacePolishEnabled && !mIsFeedGoneImprovementEnabled) return; |
| |
| // TODO(crbug.com/1315676): Clean up this code when the refactor is enabled. |
| mPropertyModel.set( |
| MV_TILES_CONTAINER_TOP_MARGIN, |
| resources.getDimensionPixelSize(R.dimen.mv_tiles_container_top_margin)); |
| |
| // If improving Start surface when Feed is disabled is needed, mvt grid layout (two row) is |
| // shown. |
| if (mIsFeedGoneImprovementEnabled) { |
| mPropertyModel.set( |
| MV_TILES_CONTAINER_TOP_MARGIN, |
| resources.getDimensionPixelOffset(R.dimen.tile_grid_layout_top_margin) |
| + resources.getDimensionPixelOffset( |
| R.dimen.ntp_search_box_bottom_margin)); |
| mPropertyModel.set( |
| MV_TILES_CONTAINER_LEFT_RIGHT_MARGIN, |
| resources.getDimensionPixelSize(R.dimen.ntp_header_lateral_paddings_v2)); |
| if (isSingleTabSwitcher()) { |
| mPropertyModel.set( |
| SINGLE_TAB_TOP_MARGIN, |
| resources.getDimensionPixelOffset( |
| R.dimen.single_tab_view_top_margin_for_feed_improvement)); |
| } |
| } |
| } |
| |
| @Override |
| public void onResumeWithNative() { |
| mayRecordHomepageSessionBegin(); |
| } |
| |
| @Override |
| public void onPauseWithNative() { |
| mayRecordHomepageSessionEnd(); |
| } |
| |
| /** Records UMA for the time spent on Start Surface. */ |
| private void recordTimeSpendInStart() { |
| RecordHistogram.recordMediumTimesHistogram( |
| "StartSurface.TimeSpent", System.currentTimeMillis() - mLastShownTimeMs); |
| } |
| |
| /** |
| * Records the UMA between the time that a start surface appears and the time at which a spare |
| * tab creation is initiated. |
| */ |
| private void recordTimeBetweenShowAndCreate() { |
| RecordHistogram.recordMediumTimesHistogram( |
| "StartSurface.SpareTab.TimeBetweenShowAndCreate", |
| System.currentTimeMillis() - mLastShownTimeMs); |
| } |
| |
| /** |
| * If mLastShownTimeMs is set as an actual time and hasn't been recorded by other start surface |
| * hiding actions, the recordTimeSpendInStart function will be called. We then reset |
| * mLastShownTimeMs as the default value in case it will be recorded again by another hiding |
| * action. |
| */ |
| void mayRecordHomepageSessionEnd() { |
| if (mLastShownTimeMs != LAST_SHOW_TIME_NOT_SET) { |
| recordTimeSpendInStart(); |
| mLastShownTimeMs = LAST_SHOW_TIME_NOT_SET; |
| } |
| } |
| |
| private void mayRecordHomepageSessionBegin() { |
| if (isHomepageShown() && mLastShownTimeMs == LAST_SHOW_TIME_NOT_SET) { |
| mLastShownTimeMs = System.currentTimeMillis(); |
| } |
| } |
| |
| /** Returns whether the Start surface homepage is showing. */ |
| @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) |
| boolean isHomepageShown() { |
| return mIsStartSurfaceRefactorEnabled |
| ? mIsHomepageShown |
| : mStartSurfaceState == StartSurfaceState.SHOWN_HOMEPAGE; |
| } |
| |
| public FeedActionDelegate getFeedActionDelegateForTesting() { |
| assert mPropertyModel.get(EXPLORE_SURFACE_COORDINATOR) != null; |
| return mPropertyModel |
| .get(EXPLORE_SURFACE_COORDINATOR) |
| .getFeedActionDelegateForTesting(); // IN-TEST |
| } |
| |
| TabSwitcher getTabSwitcherModuleForTesting() { |
| return mTabSwitcherModule; |
| } |
| |
| Controller getControllerForTesting() { |
| return mController; |
| } |
| |
| Runnable getInitializeMVTilesRunnableForTesting() { |
| return mInitializeMVTilesRunnable; |
| } |
| |
| /** |
| * Update the background color of the start surface based on whether it is polished or not , |
| * in the incognito mode or non-incognito mode. |
| */ |
| @VisibleForTesting |
| void updateBackgroundColor(PropertyModel propertyModel) { |
| @ColorInt int surfaceBackgroundColor; |
| if (mIsSurfacePolishEnabled && !mIsIncognito) { |
| surfaceBackgroundColor = |
| ChromeColors.getSurfaceColor( |
| mContext, R.dimen.home_surface_background_color_elevation); |
| } else { |
| surfaceBackgroundColor = ChromeColors.getPrimaryBackgroundColor(mContext, mIsIncognito); |
| } |
| propertyModel.set(BACKGROUND_COLOR, surfaceBackgroundColor); |
| } |
| |
| void setIsIncognitoForTesting(boolean isIncognito) { |
| mIsIncognito = isIncognito; |
| } |
| } |