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 android.view.View; 8 import android.view.View.OnClickListener; 9 import android.view.View.OnLongClickListener; 10 import android.view.ViewStub; 11 12 import androidx.annotation.VisibleForTesting; 13 14 import org.chromium.base.CallbackController; 15 import org.chromium.base.library_loader.LibraryLoader; 16 import org.chromium.base.supplier.ObservableSupplier; 17 import org.chromium.base.supplier.OneshotSupplier; 18 import org.chromium.base.supplier.Supplier; 19 import org.chromium.chrome.R; 20 import org.chromium.chrome.browser.incognito.IncognitoUtils; 21 import org.chromium.chrome.browser.layouts.LayoutStateProvider; 22 import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider; 23 import org.chromium.chrome.browser.tabmodel.TabModelSelector; 24 import org.chromium.chrome.browser.toolbar.ButtonData; 25 import org.chromium.chrome.browser.toolbar.TabCountProvider; 26 import org.chromium.chrome.browser.toolbar.TabSwitcherButtonCoordinator; 27 import org.chromium.chrome.browser.toolbar.TabSwitcherButtonView; 28 import org.chromium.chrome.browser.toolbar.ThemeColorProvider; 29 import org.chromium.chrome.browser.toolbar.menu_button.MenuButtonCoordinator; 30 import org.chromium.chrome.browser.user_education.UserEducationHelper; 31 import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration; 32 import org.chromium.chrome.features.start_surface.StartSurfaceState; 33 import org.chromium.ui.modelutil.PropertyModel; 34 import org.chromium.ui.modelutil.PropertyModelChangeProcessor; 35 36 /** 37 * The controller for the StartSurfaceToolbar. This class handles all interactions that the 38 * StartSurfaceToolbar has with the outside world. Lazily creates the tab toolbar the first time 39 * it's needed. 40 */ 41 public class StartSurfaceToolbarCoordinator { 42 private final StartSurfaceToolbarMediator mToolbarMediator; 43 private final ViewStub mStub; 44 private final PropertyModel mPropertyModel; 45 private PropertyModelChangeProcessor mPropertyModelChangeProcessor; 46 private StartSurfaceToolbarView mView; 47 private TabModelSelector mTabModelSelector; 48 private IncognitoSwitchCoordinator mIncognitoSwitchCoordinator; 49 private TabSwitcherButtonCoordinator mTabSwitcherButtonCoordinator; 50 private TabSwitcherButtonView mTabSwitcherButtonView; 51 private TabCountProvider mTabCountProvider; 52 private ThemeColorProvider mThemeColorProvider; 53 private OnClickListener mTabSwitcherClickListener; 54 private OnLongClickListener mTabSwitcherLongClickListener; 55 private MenuButtonCoordinator mMenuButtonCoordinator; 56 private CallbackController mCallbackController = new CallbackController(); 57 StartSurfaceToolbarCoordinator(ViewStub startSurfaceToolbarStub, UserEducationHelper userEducationHelper, OneshotSupplier<LayoutStateProvider> layoutStateProviderSupplier, ObservableSupplier<Boolean> identityDiscStateSupplier, ThemeColorProvider provider, MenuButtonCoordinator menuButtonCoordinator, Supplier<ButtonData> identityDiscButtonSupplier)58 StartSurfaceToolbarCoordinator(ViewStub startSurfaceToolbarStub, 59 UserEducationHelper userEducationHelper, 60 OneshotSupplier<LayoutStateProvider> layoutStateProviderSupplier, 61 ObservableSupplier<Boolean> identityDiscStateSupplier, ThemeColorProvider provider, 62 MenuButtonCoordinator menuButtonCoordinator, 63 Supplier<ButtonData> identityDiscButtonSupplier) { 64 mStub = startSurfaceToolbarStub; 65 66 layoutStateProviderSupplier.onAvailable( 67 mCallbackController.makeCancelable(this::setLayoutStateProvider)); 68 69 mPropertyModel = 70 new PropertyModel.Builder(StartSurfaceToolbarProperties.ALL_KEYS) 71 .with(StartSurfaceToolbarProperties.INCOGNITO_SWITCHER_VISIBLE, 72 !StartSurfaceConfiguration 73 .START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB 74 .getValue() 75 && !StartSurfaceConfiguration 76 .START_SURFACE_HIDE_INCOGNITO_SWITCH.getValue()) 77 .with(StartSurfaceToolbarProperties.IN_START_SURFACE_MODE, false) 78 .with(StartSurfaceToolbarProperties.MENU_IS_VISIBLE, true) 79 .with(StartSurfaceToolbarProperties.IS_VISIBLE, true) 80 .build(); 81 82 // START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB and START_SURFACE_SHOW_STACK_TAB_SWITCHER 83 // should not be both true. 84 assert !(StartSurfaceConfiguration.START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB.getValue() 85 && StartSurfaceConfiguration.START_SURFACE_SHOW_STACK_TAB_SWITCHER.getValue()); 86 mToolbarMediator = new StartSurfaceToolbarMediator(mPropertyModel, 87 (iphCommandBuilder) 88 -> { 89 // TODO(crbug.com/865801): Replace the null check with an assert after fixing or 90 // removing the ShareButtonControllerTest that necessitated it. 91 if (mView == null) return; 92 userEducationHelper.requestShowIPH( 93 iphCommandBuilder.setAnchorView(mView.getIdentityDiscView()).build()); 94 }, 95 StartSurfaceConfiguration.START_SURFACE_HIDE_INCOGNITO_SWITCH_NO_TAB.getValue(), 96 StartSurfaceConfiguration.START_SURFACE_HIDE_INCOGNITO_SWITCH.getValue(), 97 StartSurfaceConfiguration.START_SURFACE_SHOW_STACK_TAB_SWITCHER.getValue(), 98 menuButtonCoordinator, identityDiscStateSupplier, identityDiscButtonSupplier); 99 100 mThemeColorProvider = provider; 101 mMenuButtonCoordinator = menuButtonCoordinator; 102 } 103 104 /** 105 * Cleans up any code and removes observers as necessary. 106 */ destroy()107 void destroy() { 108 mToolbarMediator.destroy(); 109 if (mIncognitoSwitchCoordinator != null) mIncognitoSwitchCoordinator.destroy(); 110 if (mTabSwitcherButtonCoordinator != null) mTabSwitcherButtonCoordinator.destroy(); 111 if (mMenuButtonCoordinator != null) { 112 mMenuButtonCoordinator.destroy(); 113 mMenuButtonCoordinator = null; 114 } 115 if (mCallbackController != null) { 116 mCallbackController.destroy(); 117 mCallbackController = null; 118 } 119 mTabSwitcherButtonCoordinator = null; 120 mTabSwitcherButtonView = null; 121 mTabCountProvider = null; 122 mThemeColorProvider = null; 123 mTabSwitcherClickListener = null; 124 mTabSwitcherLongClickListener = null; 125 } 126 127 /** 128 * Sets the OnClickListener that will be notified when the New Tab button is pressed. 129 * @param listener The callback that will be notified when the New Tab button is pressed. 130 */ setOnNewTabClickHandler(View.OnClickListener listener)131 void setOnNewTabClickHandler(View.OnClickListener listener) { 132 mToolbarMediator.setOnNewTabClickHandler(listener); 133 } 134 135 /** 136 * Sets the current {@Link TabModelSelector} so the toolbar can pass it into buttons that need 137 * access to it. 138 */ setTabModelSelector(TabModelSelector selector)139 void setTabModelSelector(TabModelSelector selector) { 140 mTabModelSelector = selector; 141 mToolbarMediator.setTabModelSelector(selector); 142 } 143 144 /** 145 * Called when Start Surface mode is entered or exited. 146 * @param inStartSurfaceMode Whether or not start surface mode should be shown or hidden. 147 */ setStartSurfaceMode(boolean inStartSurfaceMode)148 void setStartSurfaceMode(boolean inStartSurfaceMode) { 149 if (!isInflated()) { 150 inflate(); 151 } 152 mToolbarMediator.setStartSurfaceMode(inStartSurfaceMode); 153 } 154 155 /** 156 * @param provider The provider used to determine incognito state. 157 */ setIncognitoStateProvider(IncognitoStateProvider provider)158 void setIncognitoStateProvider(IncognitoStateProvider provider) { 159 mToolbarMediator.setIncognitoStateProvider(provider); 160 } 161 162 /** 163 * Called when accessibility status changes. 164 * @param enabled whether accessibility status is enabled. 165 */ onAccessibilityStatusChanged(boolean enabled)166 void onAccessibilityStatusChanged(boolean enabled) { 167 mToolbarMediator.onAccessibilityStatusChanged(enabled); 168 } 169 170 /** 171 * @param layoutStateProvider The {@link LayoutStateProvider} to observe layout state changes. 172 */ setLayoutStateProvider(LayoutStateProvider layoutStateProvider)173 private void setLayoutStateProvider(LayoutStateProvider layoutStateProvider) { 174 assert layoutStateProvider != null; 175 mToolbarMediator.setLayoutStateProvider(layoutStateProvider); 176 } 177 178 /** 179 * @param tabCountProvider The {@link TabCountProvider} to update the tab switcher button. 180 */ setTabCountProvider(TabCountProvider tabCountProvider)181 void setTabCountProvider(TabCountProvider tabCountProvider) { 182 if (mTabSwitcherButtonCoordinator != null) { 183 mTabSwitcherButtonCoordinator.setTabCountProvider(tabCountProvider); 184 } else { 185 mTabCountProvider = tabCountProvider; 186 } 187 } 188 189 /** 190 * @param onClickListener The {@link OnClickListener} for the tab switcher button. 191 */ setTabSwitcherListener(OnClickListener onClickListener)192 void setTabSwitcherListener(OnClickListener onClickListener) { 193 if (mTabSwitcherButtonCoordinator != null) { 194 mTabSwitcherButtonCoordinator.setTabSwitcherListener(onClickListener); 195 } else { 196 mTabSwitcherClickListener = onClickListener; 197 } 198 } 199 200 /** 201 * @param listener The {@link OnLongClickListener} for the tab switcher button. 202 */ setOnTabSwitcherLongClickHandler(OnLongClickListener listener)203 void setOnTabSwitcherLongClickHandler(OnLongClickListener listener) { 204 if (mTabSwitcherButtonView != null) { 205 mTabSwitcherButtonView.setOnLongClickListener(listener); 206 } else { 207 mTabSwitcherLongClickListener = listener; 208 } 209 } 210 211 /** 212 * Called when start surface state is changed. 213 * @param newState The new {@link StartSurfaceState}. 214 * @param shouldShowStartSurfaceToolbar Whether or not should show start surface toolbar. 215 */ onStartSurfaceStateChanged( @tartSurfaceState int newState, boolean shouldShowStartSurfaceToolbar)216 void onStartSurfaceStateChanged( 217 @StartSurfaceState int newState, boolean shouldShowStartSurfaceToolbar) { 218 mToolbarMediator.onStartSurfaceStateChanged(newState, shouldShowStartSurfaceToolbar); 219 } 220 221 /** 222 * Triggered when the offset of start surface header view is changed. 223 * @param verticalOffset The start surface header view's offset. 224 */ onStartSurfaceHeaderOffsetChanged(int verticalOffset)225 void onStartSurfaceHeaderOffsetChanged(int verticalOffset) { 226 mToolbarMediator.onStartSurfaceHeaderOffsetChanged(verticalOffset); 227 } 228 229 /** 230 * @param toolbarHeight The height of start surface toolbar. 231 * @return Whether or not toolbar container view should be hidden. 232 */ shouldHideToolbarContainer(int toolbarHeight)233 boolean shouldHideToolbarContainer(int toolbarHeight) { 234 return mToolbarMediator.shouldHideToolbarContainer(toolbarHeight); 235 } 236 onNativeLibraryReady()237 void onNativeLibraryReady() { 238 // It is possible that the {@link mIncognitoSwitchCoordinator} isn't created because 239 // inflate() is called when the native library isn't ready. So create it now. 240 if (isInflated()) { 241 assert mTabModelSelector != null; 242 maybeCreateIncognitoSwitchCoordinator(); 243 } 244 mToolbarMediator.onNativeLibraryReady(); 245 } 246 247 /** 248 * @param highlight If the new tab button should be highlighted. 249 */ setNewTabButtonHighlight(boolean highlight)250 void setNewTabButtonHighlight(boolean highlight) { 251 mToolbarMediator.setNewTabButtonHighlight(highlight); 252 } 253 inflate()254 private void inflate() { 255 mStub.setLayoutResource(R.layout.start_top_toolbar); 256 mView = (StartSurfaceToolbarView) mStub.inflate(); 257 mMenuButtonCoordinator.setMenuButton(mView.findViewById(R.id.menu_button_wrapper)); 258 mMenuButtonCoordinator.setVisibility( 259 mPropertyModel.get(StartSurfaceToolbarProperties.MENU_IS_VISIBLE)); 260 mPropertyModelChangeProcessor = PropertyModelChangeProcessor.create( 261 mPropertyModel, mView, StartSurfaceToolbarViewBinder::bind); 262 if (LibraryLoader.getInstance().isInitialized()) { 263 maybeCreateIncognitoSwitchCoordinator(); 264 } 265 266 if (StartSurfaceConfiguration.START_SURFACE_SHOW_STACK_TAB_SWITCHER.getValue()) { 267 mTabSwitcherButtonView = mView.findViewById(R.id.start_tab_switcher_button); 268 if (mTabSwitcherLongClickListener != null) { 269 mTabSwitcherButtonView.setOnLongClickListener(mTabSwitcherLongClickListener); 270 mTabSwitcherLongClickListener = null; 271 } 272 mTabSwitcherButtonCoordinator = 273 new TabSwitcherButtonCoordinator(mTabSwitcherButtonView); 274 mTabSwitcherButtonCoordinator.setThemeColorProvider(mThemeColorProvider); 275 mTabSwitcherButtonView.setVisibility(View.VISIBLE); 276 if (mTabCountProvider != null) { 277 mTabSwitcherButtonCoordinator.setTabCountProvider(mTabCountProvider); 278 mTabCountProvider = null; 279 } 280 if (mTabSwitcherClickListener != null) { 281 mTabSwitcherButtonCoordinator.setTabSwitcherListener(mTabSwitcherClickListener); 282 mTabSwitcherClickListener = null; 283 } 284 } 285 } 286 maybeCreateIncognitoSwitchCoordinator()287 private void maybeCreateIncognitoSwitchCoordinator() { 288 if (mIncognitoSwitchCoordinator != null || mTabModelSelector == null) { 289 return; 290 } 291 292 if (IncognitoUtils.isIncognitoModeEnabled() 293 && !StartSurfaceConfiguration.START_SURFACE_SHOW_STACK_TAB_SWITCHER.getValue()) { 294 mIncognitoSwitchCoordinator = new IncognitoSwitchCoordinator(mView, mTabModelSelector); 295 } 296 } 297 isInflated()298 private boolean isInflated() { 299 return mView != null; 300 } 301 302 @VisibleForTesting getIncognitoSwitchCoordinatorForTesting()303 public IncognitoSwitchCoordinator getIncognitoSwitchCoordinatorForTesting() { 304 return mIncognitoSwitchCoordinator; 305 } 306 } 307