1 // Copyright 2020 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.compositor.overlays.toolbar;
6 
7 import android.content.Context;
8 
9 import androidx.annotation.ColorInt;
10 import androidx.annotation.VisibleForTesting;
11 
12 import org.chromium.base.Callback;
13 import org.chromium.chrome.browser.ActivityTabProvider;
14 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
15 import org.chromium.chrome.browser.browser_controls.BrowserControlsUtils;
16 import org.chromium.chrome.browser.layouts.LayoutStateProvider;
17 import org.chromium.chrome.browser.layouts.LayoutStateProvider.LayoutStateObserver;
18 import org.chromium.chrome.browser.layouts.LayoutType;
19 import org.chromium.chrome.browser.tab.EmptyTabObserver;
20 import org.chromium.chrome.browser.tab.Tab;
21 import org.chromium.chrome.browser.tab.TabObserver;
22 import org.chromium.chrome.browser.tasks.tab_management.TabUiFeatureUtilities;
23 import org.chromium.chrome.browser.toolbar.ToolbarColors;
24 import org.chromium.components.browser_ui.widget.ClipDrawableProgressBar;
25 import org.chromium.ui.base.DeviceFormFactor;
26 import org.chromium.ui.modelutil.PropertyModel;
27 
28 /** The business logic for controlling the top toolbar's cc texture. */
29 public class TopToolbarOverlayMediator {
30     // Forced testing params.
31     private static Boolean sIsTabletForTesting;
32     private static Integer sToolbarBackgroundColorForTesting;
33     private static Integer sUrlBarColorForTesting;
34 
35     /** An Android Context. */
36     private final Context mContext;
37 
38     /** A handle to the layout manager for observing scene changes. */
39     private final LayoutStateProvider mLayoutStateProvider;
40 
41     /** The observer of changes to the active layout. */
42     private final LayoutStateObserver mSceneChangeObserver;
43 
44     /** A means of populating draw info for the progress bar. */
45     private final Callback<ClipDrawableProgressBar.DrawingInfo> mProgressInfoCallback;
46 
47     /** Provides current tab. */
48     private final ActivityTabProvider mTabSupplier;
49 
50     /** An observer that watches for changes in the active tab. */
51     private final ActivityTabProvider.ActivityTabObserver mTabSupplierObserver;
52 
53     /** Access to the current state of the browser controls. */
54     private final BrowserControlsStateProvider mBrowserControlsStateProvider;
55 
56     /** An observer of the browser controls offsets. */
57     private final BrowserControlsStateProvider.Observer mBrowserControlsObserver;
58 
59     /** The view state for this overlay. */
60     private final PropertyModel mModel;
61 
62     /** The last non-null tab. */
63     private Tab mLastActiveTab;
64 
65     /** Whether the active layout has its own toolbar to display instead of this one. */
66     private boolean mLayoutHasOwnToolbar;
67 
68     /** Whether the android view for this overlay is visible. */
69     private boolean mIsAndroidViewVisible;
70 
TopToolbarOverlayMediator(PropertyModel model, Context context, LayoutStateProvider layoutStateProvider, Callback<ClipDrawableProgressBar.DrawingInfo> progressInfoCallback, ActivityTabProvider tabSupplier, BrowserControlsStateProvider browserControlsStateProvider)71     TopToolbarOverlayMediator(PropertyModel model, Context context,
72             LayoutStateProvider layoutStateProvider,
73             Callback<ClipDrawableProgressBar.DrawingInfo> progressInfoCallback,
74             ActivityTabProvider tabSupplier,
75             BrowserControlsStateProvider browserControlsStateProvider) {
76         mContext = context;
77         mLayoutStateProvider = layoutStateProvider;
78         mProgressInfoCallback = progressInfoCallback;
79         mTabSupplier = tabSupplier;
80         mBrowserControlsStateProvider = browserControlsStateProvider;
81         mModel = model;
82 
83         mSceneChangeObserver = new LayoutStateObserver() {
84             @Override
85             public void onStartedShowing(@LayoutType int layout, boolean showToolbar) {
86                 // TODO(1100332): Once ToolbarSwipeLayout uses a SceneLayer that does not include
87                 //                its own toolbar, only check for the vertical tab switcher.
88                 mLayoutHasOwnToolbar = (layout == LayoutType.TAB_SWITCHER
89                                                && !TabUiFeatureUtilities.isGridTabSwitcherEnabled())
90                         || layout == LayoutType.TOOLBAR_SWIPE;
91                 updateVisibility();
92             }
93         };
94         mLayoutStateProvider.addObserver(mSceneChangeObserver);
95 
96         final TabObserver currentTabObserver = new EmptyTabObserver() {
97             @Override
98             public void onDidChangeThemeColor(Tab tab, int color) {
99                 updateThemeColor(tab);
100             }
101 
102             @Override
103             public void onLoadProgressChanged(Tab tab, float progress) {
104                 updateProgress();
105             }
106 
107             @Override
108             public void onContentChanged(Tab tab) {
109                 updateVisibility();
110                 updateThemeColor(tab);
111             }
112         };
113 
114         // Keep an observer attached to the visible tab (and only the visible tab) to update
115         // properties including theme color.
116         mTabSupplierObserver = (tab, hint) -> {
117             if (mLastActiveTab != null) mLastActiveTab.removeObserver(currentTabObserver);
118             if (tab == null) return;
119 
120             mLastActiveTab = tab;
121             mLastActiveTab.addObserver(currentTabObserver);
122             updateVisibility();
123             updateThemeColor(mLastActiveTab);
124             updateProgress();
125         };
126         mTabSupplier.addObserverAndTrigger(mTabSupplierObserver);
127 
128         mBrowserControlsObserver = new BrowserControlsStateProvider.Observer() {
129             @Override
130             public void onControlsOffsetChanged(int topOffset, int topControlsMinHeightOffset,
131                     int bottomOffset, int bottomControlsMinHeightOffset, boolean needsAnimate) {
132                 // The content offset is passed to the toolbar layer so that it can position itself
133                 // at the bottom of the space available for top controls. The main reason for using
134                 // content offset instead of top controls offset is that top controls can have a
135                 // greater height than that of the toolbar, e.g. when status indicator is visible,
136                 // and the toolbar needs to be positioned at the bottom of the top controls
137                 // regardless of the total height.
138                 mModel.set(TopToolbarOverlayProperties.CONTENT_OFFSET,
139                         mBrowserControlsStateProvider.getContentOffset());
140 
141                 updateVisibility();
142                 updateShadowState();
143             }
144         };
145         mBrowserControlsStateProvider.addObserver(mBrowserControlsObserver);
146     }
147 
148     /**
149      * Set whether the android view corresponding with this overlay is showing.
150      * @param isVisible Whether the android view is visible.
151      */
setIsAndroidViewVisible(boolean isVisible)152     void setIsAndroidViewVisible(boolean isVisible) {
153         mIsAndroidViewVisible = isVisible;
154         updateShadowState();
155     }
156 
157     /**
158      * Compute whether the texture's shadow should be visible. The shadow is visible whenever the
159      * android view is not shown.
160      */
updateShadowState()161     private void updateShadowState() {
162         boolean drawControlsAsTexture =
163                 BrowserControlsUtils.drawControlsAsTexture(mBrowserControlsStateProvider);
164         boolean showShadow = drawControlsAsTexture || !mIsAndroidViewVisible;
165         mModel.set(TopToolbarOverlayProperties.SHOW_SHADOW, showShadow);
166     }
167 
168     /**
169      * Update the colors of the layer based on the specified tab.
170      * @param tab The tab to base the colors on.
171      */
updateThemeColor(Tab tab)172     private void updateThemeColor(Tab tab) {
173         @ColorInt
174         int color = getToolbarBackgroundColor(tab);
175         mModel.set(TopToolbarOverlayProperties.TOOLBAR_BACKGROUND_COLOR, color);
176         mModel.set(TopToolbarOverlayProperties.URL_BAR_COLOR, getUrlBarBackgroundColor(tab, color));
177     }
178 
179     /**
180      * @param tab The tab to get the background color for.
181      * @return The background color.
182      */
183     @ColorInt
getToolbarBackgroundColor(Tab tab)184     private int getToolbarBackgroundColor(Tab tab) {
185         if (sToolbarBackgroundColorForTesting != null) return sToolbarBackgroundColorForTesting;
186         return ToolbarColors.getToolbarSceneLayerBackground(tab);
187     }
188 
189     /**
190      * @param tab The tab to get the background color for.
191      * @param backgroundColor The tab's background color.
192      * @return The url bar color.
193      */
194     @ColorInt
getUrlBarBackgroundColor(Tab tab, @ColorInt int backgroundColor)195     private int getUrlBarBackgroundColor(Tab tab, @ColorInt int backgroundColor) {
196         if (sUrlBarColorForTesting != null) return sUrlBarColorForTesting;
197         return ToolbarColors.getTextBoxColorForToolbarBackground(
198                 mContext.getResources(), tab, backgroundColor);
199     }
200 
201     /** Update the state of the composited progress bar. */
updateProgress()202     private void updateProgress() {
203         // Tablets have their own version of a progress "spinner".
204         if (isTablet()) return;
205 
206         if (mModel.get(TopToolbarOverlayProperties.PROGRESS_BAR_INFO) == null) {
207             mModel.set(TopToolbarOverlayProperties.PROGRESS_BAR_INFO,
208                     new ClipDrawableProgressBar.DrawingInfo());
209         }
210 
211         // Update and set the progress info to trigger an update; the PROGRESS_BAR_INFO
212         // property skips the object equality check.
213         mProgressInfoCallback.onResult(mModel.get(TopToolbarOverlayProperties.PROGRESS_BAR_INFO));
214         mModel.set(TopToolbarOverlayProperties.PROGRESS_BAR_INFO,
215                 mModel.get(TopToolbarOverlayProperties.PROGRESS_BAR_INFO));
216     }
217 
218     /** @return Whether this component is in tablet mode. */
isTablet()219     private boolean isTablet() {
220         if (sIsTabletForTesting != null) return sIsTabletForTesting;
221         return DeviceFormFactor.isNonMultiDisplayContextOnTablet(mContext);
222     }
223 
224     /** Clean up any state and observers. */
destroy()225     void destroy() {
226         mTabSupplier.removeObserver(mTabSupplierObserver);
227         mTabSupplierObserver.onActivityTabChanged(null, false);
228         mLastActiveTab = null;
229 
230         mLayoutStateProvider.removeObserver(mSceneChangeObserver);
231         mBrowserControlsStateProvider.removeObserver(mBrowserControlsObserver);
232     }
233 
234     /** Update the visibility of the overlay. */
updateVisibility()235     private void updateVisibility() {
236         mModel.set(TopToolbarOverlayProperties.VISIBLE,
237                 !BrowserControlsUtils.areBrowserControlsOffScreen(mBrowserControlsStateProvider)
238                         && !mLayoutHasOwnToolbar);
239     }
240 
241     /** @return Whether this overlay should be attached to the tree. */
shouldBeAttachedToTree()242     boolean shouldBeAttachedToTree() {
243         return true;
244     }
245 
246     @VisibleForTesting
setIsTabletForTesting(Boolean isTablet)247     static void setIsTabletForTesting(Boolean isTablet) {
248         sIsTabletForTesting = isTablet;
249     }
250 
251     @VisibleForTesting
setToolbarBackgroundColorForTesting(@olorInt int color)252     static void setToolbarBackgroundColorForTesting(@ColorInt int color) {
253         sToolbarBackgroundColorForTesting = color;
254     }
255 
256     @VisibleForTesting
setUrlBarColorForTesting(@olorInt int color)257     static void setUrlBarColorForTesting(@ColorInt int color) {
258         sUrlBarColorForTesting = color;
259     }
260 }
261