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.content.Context;
8 import android.content.res.ColorStateList;
9 import android.graphics.Rect;
10 import android.graphics.drawable.Drawable;
11 import android.os.Build;
12 import android.util.AttributeSet;
13 import android.view.View;
14 import android.view.ViewPropertyAnimator;
15 import android.widget.ImageButton;
16 import android.widget.RelativeLayout;
17 
18 import androidx.annotation.Nullable;
19 import androidx.annotation.StringRes;
20 import androidx.appcompat.content.res.AppCompatResources;
21 
22 import org.chromium.chrome.R;
23 import org.chromium.chrome.browser.device.DeviceClassManager;
24 import org.chromium.chrome.browser.tabmodel.IncognitoStateProvider;
25 import org.chromium.chrome.browser.toolbar.NewTabButton;
26 import org.chromium.components.browser_ui.styles.ChromeColors;
27 import org.chromium.components.browser_ui.widget.animation.Interpolators;
28 import org.chromium.components.browser_ui.widget.highlight.ViewHighlighter;
29 
30 /** View of the StartSurfaceToolbar */
31 class StartSurfaceToolbarView extends RelativeLayout {
32     private NewTabButton mNewTabButton;
33     private View mIncognitoSwitch;
34     private View mLogo;
35     @Nullable
36     private ImageButton mIdentityDiscButton;
37     private int mPrimaryColor;
38     private ColorStateList mLightIconTint;
39     private ColorStateList mDarkIconTint;
40     private ViewPropertyAnimator mVisibilityAnimator;
41 
42     private Rect mLogoRect = new Rect();
43     private Rect mViewRect = new Rect();
44 
45     private boolean mShouldShow;
46     private boolean mInStartSurfaceMode;
47     private boolean mIsShowing;
48 
StartSurfaceToolbarView(Context context, AttributeSet attrs)49     public StartSurfaceToolbarView(Context context, AttributeSet attrs) {
50         super(context, attrs);
51     }
52 
53     @Override
onFinishInflate()54     protected void onFinishInflate() {
55         super.onFinishInflate();
56         mNewTabButton = findViewById(R.id.new_tab_button);
57         mIncognitoSwitch = findViewById(R.id.incognito_switch);
58         mLogo = findViewById(R.id.logo);
59         mIdentityDiscButton = findViewById(R.id.identity_disc_button);
60         updatePrimaryColorAndTint(false);
61     }
62 
63     @Override
onLayout(boolean changed, int l, int t, int r, int b)64     protected void onLayout(boolean changed, int l, int t, int r, int b) {
65         // TODO(https://crbug.com/1040526)
66 
67         super.onLayout(changed, l, t, r, b);
68 
69         if (mLogo.getVisibility() == View.GONE) return;
70 
71         mLogoRect.set(mLogo.getLeft(), mLogo.getTop(), mLogo.getRight(), mLogo.getBottom());
72         for (int viewIndex = 0; viewIndex < getChildCount(); viewIndex++) {
73             View view = getChildAt(viewIndex);
74             if (view == mLogo || view.getVisibility() == View.GONE) continue;
75 
76             assert view.getVisibility() == View.VISIBLE;
77             mViewRect.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
78             if (Rect.intersects(mLogoRect, mViewRect)) {
79                 mLogo.setVisibility(View.GONE);
80                 break;
81             }
82         }
83     }
84 
85     /**
86      * Sets the {@link OnClickListener} that will be notified when the New Tab button is pressed.
87      * @param listener The callback that will be notified when the New Tab button is pressed.
88      */
setOnNewTabClickHandler(View.OnClickListener listener)89     void setOnNewTabClickHandler(View.OnClickListener listener) {
90         mNewTabButton.setOnClickListener(listener);
91     }
92 
93     /**
94      * Sets the Logo visibility. Logo will not show if screen is not wide enough.
95      * @param isVisible Whether the Logo should be visible.
96      */
setLogoVisibility(boolean isVisible)97     void setLogoVisibility(boolean isVisible) {
98         mLogo.setVisibility(isVisible ? View.VISIBLE : View.GONE);
99     }
100 
101     /**
102      * @param isVisible Whether the menu button is visible.
103      */
setMenuButtonVisibility(boolean isVisible)104     void setMenuButtonVisibility(boolean isVisible) {
105         final int buttonPaddingLeft = getContext().getResources().getDimensionPixelOffset(
106                 R.dimen.start_surface_toolbar_button_padding_to_button);
107         final int buttonPaddingRight =
108                 (isVisible ? buttonPaddingLeft
109                            : getContext().getResources().getDimensionPixelOffset(
110                                    R.dimen.start_surface_toolbar_button_padding_to_edge));
111         mIdentityDiscButton.setPadding(buttonPaddingLeft, 0, buttonPaddingRight, 0);
112         mNewTabButton.setPadding(buttonPaddingLeft, 0, buttonPaddingRight, 0);
113     }
114 
115     /**
116      * @param isVisible Whether the new tab button is visible.
117      */
setNewTabButtonVisibility(boolean isVisible)118     void setNewTabButtonVisibility(boolean isVisible) {
119         mNewTabButton.setVisibility(isVisible ? View.VISIBLE : View.GONE);
120         if (isVisible && Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
121             // This is a workaround for the issue that the UrlBar is given the default focus on
122             // Android versions before Pie when showing the start surface toolbar with the new tab
123             // button (UrlBar is invisible to users). Check crbug.com/1081538 for more details.
124             mNewTabButton.getParent().requestChildFocus(mNewTabButton, mNewTabButton);
125         }
126     }
127 
128     /**
129      * @param isClickable Whether the buttons are clickable.
130      */
setButtonClickableState(boolean isClickable)131     void setButtonClickableState(boolean isClickable) {
132         mNewTabButton.setClickable(isClickable);
133         mIncognitoSwitch.setClickable(isClickable);
134     }
135 
136     /**
137      * @param isVisible Whether the Incognito switcher is visible.
138      */
setIncognitoSwitcherVisibility(boolean isVisible)139     void setIncognitoSwitcherVisibility(boolean isVisible) {
140         mIncognitoSwitch.setVisibility(isVisible ? VISIBLE : GONE);
141     }
142 
143     /**
144      * @param isAtStart Whether the new tab button is at start.
145      */
setNewTabButtonAtStart(boolean isAtStart)146     void setNewTabButtonAtStart(boolean isAtStart) {
147         assert isAtStart;
148         if (isAtStart) {
149             ((LayoutParams) mNewTabButton.getLayoutParams()).removeRule(RelativeLayout.START_OF);
150 
151             LayoutParams params = (LayoutParams) mIncognitoSwitch.getLayoutParams();
152             params.removeRule(RelativeLayout.ALIGN_PARENT_START);
153             params.addRule(RelativeLayout.CENTER_HORIZONTAL);
154         }
155     }
156 
157     /**
158      * @param highlight If the new tab button should be highlighted.
159      */
setNewTabButtonHighlight(boolean highlight)160     void setNewTabButtonHighlight(boolean highlight) {
161         if (mNewTabButton == null) return;
162         if (highlight) {
163             ViewHighlighter.turnOnCircularHighlight(mNewTabButton);
164         } else {
165             ViewHighlighter.turnOffHighlight(mNewTabButton);
166         }
167     }
168 
169     /** Called when incognito mode changes. */
updateIncognito(boolean isIncognito)170     void updateIncognito(boolean isIncognito) {
171         updatePrimaryColorAndTint(isIncognito);
172     }
173 
174     /**
175      * @param provider The {@link IncognitoStateProvider} passed to buttons that need access to it.
176      */
setIncognitoStateProvider(IncognitoStateProvider provider)177     void setIncognitoStateProvider(IncognitoStateProvider provider) {
178         mNewTabButton.setIncognitoStateProvider(provider);
179     }
180 
181     /** Called when accessibility status changes. */
onAccessibilityStatusChanged(boolean enabled)182     void onAccessibilityStatusChanged(boolean enabled) {
183         mNewTabButton.onAccessibilityStatusChanged();
184     }
185 
186     /** @return The View for the identity disc. */
getIdentityDiscView()187     View getIdentityDiscView() {
188         return mIdentityDiscButton;
189     }
190 
191     /**
192      * @param isAtStart Whether the identity disc is at start.
193      */
setIdentityDiscAtStart(boolean isAtStart)194     void setIdentityDiscAtStart(boolean isAtStart) {
195         ((LayoutParams) mIdentityDiscButton.getLayoutParams()).removeRule(RelativeLayout.START_OF);
196     }
197 
198     /**
199      * @param isVisible Whether the identity disc is visible.
200      */
setIdentityDiscVisibility(boolean isVisible)201     void setIdentityDiscVisibility(boolean isVisible) {
202         mIdentityDiscButton.setVisibility(isVisible ? View.VISIBLE : View.GONE);
203     }
204 
205     /**
206      * Sets the {@link OnClickListener} that will be notified when the identity disc button is
207      * pressed.
208      * @param listener The callback that will be notified when the identity disc  is pressed.
209      */
setIdentityDiscClickHandler(View.OnClickListener listener)210     void setIdentityDiscClickHandler(View.OnClickListener listener) {
211         mIdentityDiscButton.setOnClickListener(listener);
212     }
213 
214     /**
215      * Updates the image displayed on the identity disc button.
216      * @param image The new image for the button.
217      */
setIdentityDiscImage(Drawable image)218     void setIdentityDiscImage(Drawable image) {
219         mIdentityDiscButton.setImageDrawable(image);
220     }
221 
222     /**
223      * Updates idnetity disc content description.
224      * @param contentDescriptionResId The new description for the button.
225      */
setIdentityDiscContentDescription(@tringRes int contentDescriptionResId)226     void setIdentityDiscContentDescription(@StringRes int contentDescriptionResId) {
227         mIdentityDiscButton.setContentDescription(
228                 getContext().getResources().getString(contentDescriptionResId));
229     }
230 
231     /**
232      * Show or hide toolbar from tab.
233      * @param inStartSurfaceMode Whether or not toolbar should be shown or hidden.
234      * */
setStartSurfaceMode(boolean inStartSurfaceMode)235     void setStartSurfaceMode(boolean inStartSurfaceMode) {
236         mInStartSurfaceMode = inStartSurfaceMode;
237         // When showing or hiding toolbar from a tab, the fade-in and fade-out animations are not
238         // needed. (eg: cold start, changing theme, changing incognito status...)
239         showStartSurfaceToolbar(mInStartSurfaceMode && mShouldShow, false);
240     }
241 
242     /**
243      * Show or hide toolbar.
244      * @param shouldShowStartSurfaceToolbar Whether or not toolbar should be shown or hidden.
245      * */
setToolbarVisibility(boolean shouldShowStartSurfaceToolbar)246     void setToolbarVisibility(boolean shouldShowStartSurfaceToolbar) {
247         mShouldShow = shouldShowStartSurfaceToolbar;
248         // When simply setting visibility, the animations should be shown. (eg: search box has
249         // focus)
250         showStartSurfaceToolbar(mInStartSurfaceMode && mShouldShow, true);
251     }
252 
253     /**
254      * Start animation to show or hide toolbar.
255      * @param showStartSurfaceToolbar Whether or not toolbar should be shown or hidden.
256      * @param showAnimation Whether or not to show the animation.
257      */
showStartSurfaceToolbar(boolean showStartSurfaceToolbar, boolean showAnimation)258     private void showStartSurfaceToolbar(boolean showStartSurfaceToolbar, boolean showAnimation) {
259         if (showStartSurfaceToolbar == mIsShowing) return;
260 
261         if (mVisibilityAnimator != null) {
262             mVisibilityAnimator.cancel();
263             finishAnimation(showStartSurfaceToolbar);
264         }
265 
266         mIsShowing = showStartSurfaceToolbar;
267 
268         if (DeviceClassManager.enableAccessibilityLayout()) {
269             finishAnimation(showStartSurfaceToolbar);
270             return;
271         }
272 
273         // TODO(https://crbug.com/1139024): Show the fade-in animation when
274         // TabUiFeatureUtilities#isTabToGtsAnimationEnabled is true.
275         if (!showAnimation) {
276             setVisibility(showStartSurfaceToolbar ? View.VISIBLE : View.GONE);
277             return;
278         }
279 
280         // Show the fade-in and fade-out animation. Set visibility as VISIBLE here to show the
281         // animation. The visibility will be finally set in finishAnimation().
282         setVisibility(View.VISIBLE);
283         setAlpha(showStartSurfaceToolbar ? 0.0f : 1.0f);
284 
285         final long duration = TopToolbarCoordinator.TAB_SWITCHER_MODE_NORMAL_ANIMATION_DURATION_MS;
286 
287         mVisibilityAnimator =
288                 animate()
289                         .alpha(showStartSurfaceToolbar ? 1.0f : 0.0f)
290                         .setDuration(duration)
291                         .setInterpolator(Interpolators.LINEAR_INTERPOLATOR)
292                         .withEndAction(() -> { finishAnimation(showStartSurfaceToolbar); });
293     }
294 
finishAnimation(boolean showStartSurfaceToolbar)295     private void finishAnimation(boolean showStartSurfaceToolbar) {
296         setAlpha(1.0f);
297         setVisibility(showStartSurfaceToolbar ? View.VISIBLE : View.GONE);
298         mVisibilityAnimator = null;
299     }
300 
updatePrimaryColorAndTint(boolean isIncognito)301     private void updatePrimaryColorAndTint(boolean isIncognito) {
302         int primaryColor = ChromeColors.getPrimaryBackgroundColor(getResources(), isIncognito);
303         setBackgroundColor(primaryColor);
304 
305         if (mLightIconTint == null) {
306             mLightIconTint = AppCompatResources.getColorStateList(
307                     getContext(), R.color.default_icon_color_light_tint_list);
308             mDarkIconTint = AppCompatResources.getColorStateList(
309                     getContext(), R.color.default_icon_color_tint_list);
310         }
311     }
312 }
313