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