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