1 // Copyright 2019 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.toolbar.top; 6 7 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.ACCESSIBILITY_ENABLED; 8 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.BUTTONS_CLICKABLE; 9 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IDENTITY_DISC_AT_START; 10 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IDENTITY_DISC_CLICK_HANDLER; 11 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IDENTITY_DISC_DESCRIPTION; 12 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IDENTITY_DISC_IMAGE; 13 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IDENTITY_DISC_IS_VISIBLE; 14 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.INCOGNITO_STATE_PROVIDER; 15 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.INCOGNITO_SWITCHER_VISIBLE; 16 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IN_START_SURFACE_MODE; 17 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IS_INCOGNITO; 18 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.IS_VISIBLE; 19 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.LOGO_IS_VISIBLE; 20 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.NEW_TAB_BUTTON_AT_START; 21 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.NEW_TAB_BUTTON_HIGHLIGHT; 22 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.NEW_TAB_BUTTON_IS_VISIBLE; 23 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.NEW_TAB_CLICK_HANDLER; 24 import static org.chromium.chrome.browser.toolbar.top.StartSurfaceToolbarProperties.TRANSLATION_Y; 25 26 import android.view.View; 27 28 import androidx.annotation.VisibleForTesting; 29 30 import org.chromium.base.Callback; 31 import org.chromium.base.CallbackController; 32 import org.chromium.base.supplier.ObservableSupplier; 33 import org.chromium.base.supplier.Supplier; 34 import org.chromium.chrome.browser.layouts.LayoutStateProvider; 35 import org.chromium.chrome.browser.layouts.LayoutType; 36 import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory; 37 import org.chromium.chrome.browser.tabmodel.EmptyTabModelSelectorObserver; 38 import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider; 39 import org.chromium.chrome.browser.tabmodel.TabModel; 40 import org.chromium.chrome.browser.tabmodel.TabModelSelector; 41 import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; 42 import org.chromium.chrome.browser.toolbar.ButtonData; 43 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator; 44 import org.chromium.chrome.browser.user_education.IPHCommandBuilder; 45 import org.chromium.chrome.browser.util.ChromeAccessibilityUtil; 46 import org.chromium.chrome.features.start_surface.StartSurfaceState; 47 import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver; 48 import org.chromium.ui.modelutil.PropertyModel; 49 50 /** The mediator implements interacts between the views and the caller. */ 51 class StartSurfaceToolbarMediator { 52 private final PropertyModel mPropertyModel; 53 private final Callback<IPHCommandBuilder> mShowIPHCallback; 54 private final boolean mHideIncognitoSwitchWhenNoTabs; 55 private final boolean mHideIncognitoSwitchOnHomePage; 56 private final boolean mShowNewTabAndIdentityDiscAtStart; 57 private final Supplier<ButtonData> mIdentityDiscButtonSupplier; 58 59 private TabModelSelector mTabModelSelector; 60 private TemplateUrlServiceObserver mTemplateUrlObserver; 61 private TabModelSelectorObserver mTabModelSelectorObserver; 62 private LayoutStateProvider mLayoutStateProvider; 63 private LayoutStateProvider.LayoutStateObserver mLayoutStateObserver; 64 private MenuButtonCoordinator mMenuButtonCoordinator; 65 @StartSurfaceState 66 private int mOverviewModeState; 67 private boolean mIsGoogleSearchEngine; 68 69 private CallbackController mCallbackController = new CallbackController(); 70 private float mNonIncognitoHomepageTranslationY; 71 StartSurfaceToolbarMediator(PropertyModel model, Callback<IPHCommandBuilder> showIPHCallback, boolean hideIncognitoSwitchWhenNoTabs, boolean hideIncognitoSwitchOnHomePage, boolean showNewTabAndIdentityDiscAtStart, MenuButtonCoordinator menuButtonCoordinator, ObservableSupplier<Boolean> identityDiscStateSupplier, Supplier<ButtonData> identityDiscButtonSupplier)72 StartSurfaceToolbarMediator(PropertyModel model, Callback<IPHCommandBuilder> showIPHCallback, 73 boolean hideIncognitoSwitchWhenNoTabs, boolean hideIncognitoSwitchOnHomePage, 74 boolean showNewTabAndIdentityDiscAtStart, MenuButtonCoordinator menuButtonCoordinator, 75 ObservableSupplier<Boolean> identityDiscStateSupplier, 76 Supplier<ButtonData> identityDiscButtonSupplier) { 77 mPropertyModel = model; 78 mOverviewModeState = StartSurfaceState.NOT_SHOWN; 79 mShowIPHCallback = showIPHCallback; 80 mHideIncognitoSwitchWhenNoTabs = hideIncognitoSwitchWhenNoTabs; 81 mHideIncognitoSwitchOnHomePage = hideIncognitoSwitchOnHomePage; 82 mShowNewTabAndIdentityDiscAtStart = showNewTabAndIdentityDiscAtStart; 83 mMenuButtonCoordinator = menuButtonCoordinator; 84 mIdentityDiscButtonSupplier = identityDiscButtonSupplier; 85 identityDiscStateSupplier.addObserver((canShowHint) -> { 86 // If the identity disc wants to be hidden and is hidden, there's nothing we need to do. 87 if (!canShowHint && !mPropertyModel.get(IDENTITY_DISC_IS_VISIBLE)) return; 88 updateIdentityDisc(mIdentityDiscButtonSupplier.get()); 89 }); 90 } 91 onNativeLibraryReady()92 void onNativeLibraryReady() { 93 assert mTemplateUrlObserver == null; 94 95 mTemplateUrlObserver = new TemplateUrlServiceObserver() { 96 @Override 97 public void onTemplateURLServiceChanged() { 98 updateLogoVisibility(TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()); 99 } 100 }; 101 102 TemplateUrlServiceFactory.get().addObserver(mTemplateUrlObserver); 103 mIsGoogleSearchEngine = TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle(); 104 updateLogoVisibility(TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle()); 105 } 106 destroy()107 void destroy() { 108 if (mTemplateUrlObserver != null) { 109 TemplateUrlServiceFactory.get().removeObserver(mTemplateUrlObserver); 110 } 111 if (mTabModelSelectorObserver != null) { 112 mTabModelSelector.removeObserver(mTabModelSelectorObserver); 113 } 114 if (mLayoutStateObserver != null) { 115 mLayoutStateProvider.removeObserver(mLayoutStateObserver); 116 } 117 if (mCallbackController != null) { 118 mCallbackController.destroy(); 119 mCallbackController = null; 120 } 121 } 122 onStartSurfaceStateChanged( @tartSurfaceState int newState, boolean shouldShowStartSurfaceToolbar)123 void onStartSurfaceStateChanged( 124 @StartSurfaceState int newState, boolean shouldShowStartSurfaceToolbar) { 125 mOverviewModeState = newState; 126 setStartSurfaceToolbarVisibility(shouldShowStartSurfaceToolbar); 127 updateIncognitoSwitchVisibility(); 128 updateNewTabButtonVisibility(); 129 updateLogoVisibility(mIsGoogleSearchEngine); 130 updateIdentityDisc(mIdentityDiscButtonSupplier.get()); 131 updateTranslationY(mNonIncognitoHomepageTranslationY); 132 } 133 onStartSurfaceHeaderOffsetChanged(int verticalOffset)134 void onStartSurfaceHeaderOffsetChanged(int verticalOffset) { 135 updateTranslationY(verticalOffset); 136 } 137 shouldHideToolbarContainer(int toolbarHeight)138 boolean shouldHideToolbarContainer(int toolbarHeight) { 139 // If it's on the non-incognito homepage, start surface toolbar is visible (omnibox has no 140 // focus), and scrolling offset is smaller than toolbar's height, we need to hide toolbar 141 // container until start surface toolbar is disappearing. 142 return mOverviewModeState == StartSurfaceState.SHOWN_HOMEPAGE 143 && !mPropertyModel.get(IS_INCOGNITO) && mPropertyModel.get(IS_VISIBLE) 144 && -mPropertyModel.get(TRANSLATION_Y) != 0 145 && -mPropertyModel.get(TRANSLATION_Y) < toolbarHeight; 146 } 147 setOnNewTabClickHandler(View.OnClickListener listener)148 void setOnNewTabClickHandler(View.OnClickListener listener) { 149 mPropertyModel.set(NEW_TAB_CLICK_HANDLER, listener); 150 } 151 setTabModelSelector(TabModelSelector selector)152 void setTabModelSelector(TabModelSelector selector) { 153 mTabModelSelector = selector; 154 155 if (mTabModelSelectorObserver == null) { 156 mTabModelSelectorObserver = new EmptyTabModelSelectorObserver() { 157 @Override 158 public void onTabModelSelected(TabModel newModel, TabModel oldModel) { 159 mPropertyModel.set(IS_INCOGNITO, mTabModelSelector.isIncognitoSelected()); 160 updateIdentityDisc(mIdentityDiscButtonSupplier.get()); 161 updateIncognitoSwitchVisibility(); 162 } 163 }; 164 } 165 mPropertyModel.set(IS_INCOGNITO, mTabModelSelector.isIncognitoSelected()); 166 updateIdentityDisc(mIdentityDiscButtonSupplier.get()); 167 mTabModelSelector.addObserver(mTabModelSelectorObserver); 168 } 169 updateIncognitoSwitchVisibility()170 private void updateIncognitoSwitchVisibility() { 171 if (mOverviewModeState == StartSurfaceState.SHOWN_HOMEPAGE && mHideIncognitoSwitchOnHomePage 172 || mShowNewTabAndIdentityDiscAtStart) { 173 mPropertyModel.set(INCOGNITO_SWITCHER_VISIBLE, false); 174 return; 175 } 176 177 if (mHideIncognitoSwitchWhenNoTabs) { 178 mPropertyModel.set(INCOGNITO_SWITCHER_VISIBLE, hasIncognitoTabs()); 179 } else { 180 mPropertyModel.set(INCOGNITO_SWITCHER_VISIBLE, true); 181 } 182 } 183 184 // TODO(crbug.com/1042997): share with TabSwitcherModeTTPhone. hasIncognitoTabs()185 private boolean hasIncognitoTabs() { 186 if (mTabModelSelector == null) return false; 187 188 // Check if there is no incognito tab, or all the incognito tabs are being closed. 189 TabModel incognitoTabModel = mTabModelSelector.getModel(true); 190 for (int i = 0; i < incognitoTabModel.getCount(); i++) { 191 if (!incognitoTabModel.getTabAt(i).isClosing()) return true; 192 } 193 return false; 194 } 195 setStartSurfaceMode(boolean inStartSurfaceMode)196 void setStartSurfaceMode(boolean inStartSurfaceMode) { 197 mPropertyModel.set(IN_START_SURFACE_MODE, inStartSurfaceMode); 198 } 199 setStartSurfaceToolbarVisibility(boolean shouldShowStartSurfaceToolbar)200 void setStartSurfaceToolbarVisibility(boolean shouldShowStartSurfaceToolbar) { 201 mPropertyModel.set(IS_VISIBLE, shouldShowStartSurfaceToolbar); 202 } 203 setIncognitoStateProvider(IncognitoStateProvider provider)204 void setIncognitoStateProvider(IncognitoStateProvider provider) { 205 mPropertyModel.set(INCOGNITO_STATE_PROVIDER, provider); 206 } 207 onAccessibilityStatusChanged(boolean enabled)208 void onAccessibilityStatusChanged(boolean enabled) { 209 mPropertyModel.set(ACCESSIBILITY_ENABLED, enabled); 210 updateNewTabButtonVisibility(); 211 } 212 setLayoutStateProvider(LayoutStateProvider layoutStateProvider)213 void setLayoutStateProvider(LayoutStateProvider layoutStateProvider) { 214 assert layoutStateProvider != null; 215 assert mLayoutStateProvider == null : "the mLayoutStateProvider should set at most once."; 216 217 mLayoutStateProvider = layoutStateProvider; 218 mLayoutStateObserver = new LayoutStateProvider.LayoutStateObserver() { 219 @Override 220 public void onStartedShowing(@LayoutType int layoutType, boolean showToolbar) { 221 if (layoutType == LayoutType.TAB_SWITCHER) { 222 updateIncognitoSwitchVisibility(); 223 if (mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_OMNIBOX_ONLY 224 || mOverviewModeState 225 == StartSurfaceState.SHOWN_TABSWITCHER_TRENDY_TERMS 226 || mShowNewTabAndIdentityDiscAtStart) { 227 mPropertyModel.set(NEW_TAB_BUTTON_AT_START, true); 228 } 229 if (mShowNewTabAndIdentityDiscAtStart) { 230 mPropertyModel.set(IDENTITY_DISC_AT_START, true); 231 } 232 } 233 } 234 @Override 235 public void onFinishedShowing(@LayoutType int layoutType) { 236 if (layoutType == LayoutType.TAB_SWITCHER) { 237 mPropertyModel.set(BUTTONS_CLICKABLE, true); 238 mMenuButtonCoordinator.setClickable(true); 239 } 240 } 241 @Override 242 public void onStartedHiding( 243 @LayoutType int layoutType, boolean showToolbar, boolean delayAnimation) { 244 if (layoutType == LayoutType.TAB_SWITCHER) { 245 mPropertyModel.set(BUTTONS_CLICKABLE, false); 246 mMenuButtonCoordinator.setClickable(false); 247 } 248 } 249 }; 250 mLayoutStateProvider.addObserver(mLayoutStateObserver); 251 } 252 253 /** 254 * @param highlight If the new tab button should be highlighted. 255 */ setNewTabButtonHighlight(boolean highlight)256 void setNewTabButtonHighlight(boolean highlight) { 257 mPropertyModel.set(NEW_TAB_BUTTON_HIGHLIGHT, highlight); 258 } 259 updateLogoVisibility(boolean isGoogleSearchEngine)260 private void updateLogoVisibility(boolean isGoogleSearchEngine) { 261 mIsGoogleSearchEngine = isGoogleSearchEngine; 262 boolean shouldShowLogo = 263 (mOverviewModeState == StartSurfaceState.SHOWN_HOMEPAGE 264 || mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_TASKS_ONLY 265 || mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_OMNIBOX_ONLY 266 || mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_TRENDY_TERMS) 267 && mIsGoogleSearchEngine; 268 mPropertyModel.set(LOGO_IS_VISIBLE, shouldShowLogo); 269 } 270 271 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) updateIdentityDisc(ButtonData buttonData)272 void updateIdentityDisc(ButtonData buttonData) { 273 boolean shouldShow = buttonData.canShow && !mTabModelSelector.isIncognitoSelected(); 274 if (shouldShow) { 275 mPropertyModel.set(IDENTITY_DISC_CLICK_HANDLER, buttonData.onClickListener); 276 // Take a defensive copy of the Drawable, since Drawables aren't immutable, and another 277 // view mutating our drawable could cause it to display incorrectly. 278 mPropertyModel.set( 279 IDENTITY_DISC_IMAGE, buttonData.drawable.getConstantState().newDrawable()); 280 mPropertyModel.set(IDENTITY_DISC_DESCRIPTION, buttonData.contentDescriptionResId); 281 mPropertyModel.set(IDENTITY_DISC_IS_VISIBLE, true); 282 mShowIPHCallback.onResult(buttonData.iphCommandBuilder); 283 } else { 284 mPropertyModel.set(IDENTITY_DISC_IS_VISIBLE, false); 285 } 286 } 287 updateNewTabButtonVisibility()288 private void updateNewTabButtonVisibility() { 289 // This toolbar is only shown for tab switcher when accessibility is enabled. Note that 290 // OverviewListLayout will be shown as the tab switcher instead of the star surface. 291 boolean isShownTabswitcherState = mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER 292 || mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_TASKS_ONLY 293 || mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_OMNIBOX_ONLY 294 || mOverviewModeState == StartSurfaceState.SHOWN_TABSWITCHER_TRENDY_TERMS 295 || ChromeAccessibilityUtil.get().isAccessibilityEnabled(); 296 mPropertyModel.set(NEW_TAB_BUTTON_IS_VISIBLE, isShownTabswitcherState); 297 } 298 updateTranslationY(float transY)299 private void updateTranslationY(float transY) { 300 if (mOverviewModeState == StartSurfaceState.SHOWN_HOMEPAGE 301 && !mPropertyModel.get(IS_INCOGNITO)) { 302 // If it's on the non-incognito homepage, document the homepage translationY. 303 mNonIncognitoHomepageTranslationY = transY; 304 // Update the translationY of the toolbarView. 305 mPropertyModel.set(TRANSLATION_Y, transY); 306 } else { 307 // If it's not on the non-incognito homepage, set the translationY as 0. 308 mPropertyModel.set(TRANSLATION_Y, 0); 309 } 310 } 311 312 @VisibleForTesting 313 @StartSurfaceState getOverviewModeStateForTesting()314 int getOverviewModeStateForTesting() { 315 return mOverviewModeState; 316 } 317 } 318