• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.setupwizardlib;
18 
19 import android.animation.ValueAnimator;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.PorterDuff;
24 import android.graphics.Rect;
25 import android.graphics.Typeface;
26 import android.graphics.drawable.Drawable;
27 import android.graphics.drawable.GradientDrawable;
28 import android.graphics.drawable.InsetDrawable;
29 import android.graphics.drawable.RippleDrawable;
30 import android.text.TextUtils;
31 import android.util.AttributeSet;
32 import android.util.Log;
33 import android.util.TypedValue;
34 import android.view.LayoutInflater;
35 import android.view.TouchDelegate;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.view.ViewStub;
39 import android.widget.Button;
40 import android.widget.ImageView;
41 import android.widget.LinearLayout;
42 import android.widget.ProgressBar;
43 import android.widget.TextView;
44 
45 import androidx.annotation.StyleRes;
46 import androidx.annotation.VisibleForTesting;
47 
48 import com.android.car.setupwizardlib.partner.PartnerConfig;
49 import com.android.car.setupwizardlib.partner.PartnerConfigHelper;
50 
51 import java.util.Locale;
52 import java.util.Objects;
53 
54 /**
55  * Custom layout for the Car Setup Wizard. Provides accessors for modifying elements such as buttons
56  * and progress bars. Any modifications to elements built by the CarSetupWizardBaseLayout should be
57  * done through methods provided by this class unless that is not possible so as to keep the state
58  * internally consistent.
59  */
60 class CarSetupWizardBaseLayout extends LinearLayout {
61     private static final String TAG = CarSetupWizardBaseLayout.class.getSimpleName();
62     private static final int ANIMATION_DURATION_MS = 100;
63 
64     private View mBackButton;
65     private View mTitleBar;
66     private Float mTitleBarElevation;
67     private TextView mToolbarTitle;
68     private PartnerConfigHelper mPartnerConfigHelper;
69 
70     /* <p>The Primary Toolbar Button should always be used when there is only a single action that
71      * moves the wizard to the next screen (e.g. Only need a 'Skip' button).
72      *
73      * When there are two actions that can move the wizard to the next screen (e.g. either 'Skip'
74      * or 'Let's Go' are the two options), then the Primary is used for the positive action
75      * while the Secondary is used for the negative action.</p>
76      */
77     private Button mPrimaryToolbarButton;
78 
79     /*
80      * Flag to track the primary toolbar button flat state.
81      */
82     private boolean mPrimaryToolbarButtonFlat;
83     private View.OnClickListener mPrimaryToolbarButtonOnClick;
84     private Button mSecondaryToolbarButton;
85     private ProgressBar mProgressBar;
86 
CarSetupWizardBaseLayout(Context context)87     CarSetupWizardBaseLayout(Context context) {
88         this(context, null);
89     }
90 
CarSetupWizardBaseLayout(Context context, @Nullable AttributeSet attrs)91     CarSetupWizardBaseLayout(Context context, @Nullable AttributeSet attrs) {
92         this(context, attrs, 0);
93     }
94 
CarSetupWizardBaseLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr)95     CarSetupWizardBaseLayout(Context context, @Nullable AttributeSet attrs,
96             int defStyleAttr) {
97         this(context, attrs, defStyleAttr, 0);
98     }
99 
100     /**
101      * On initialization, the layout gets all of the custom attributes and initializes
102      * the custom views that can be set by the user (e.g. back button, continue button).
103      */
CarSetupWizardBaseLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)104     CarSetupWizardBaseLayout(Context context, @Nullable AttributeSet attrs,
105             int defStyleAttr, int defStyleRes) {
106         super(context, attrs, defStyleAttr, defStyleRes);
107 
108         mPartnerConfigHelper = PartnerConfigHelper.get(context);
109         TypedArray attrArray = context.getTheme().obtainStyledAttributes(
110                 attrs,
111                 R.styleable.CarSetupWizardBaseLayout,
112                 0, 0);
113 
114         init(attrArray);
115     }
116 
117     /**
118      * Inflates the layout and sets the custom views (e.g. back button, continue button).
119      */
init(TypedArray attrArray)120     private void init(TypedArray attrArray) {
121         boolean showBackButton;
122 
123         boolean showToolbarTitle;
124         String toolbarTitleText;
125 
126         boolean showPrimaryToolbarButton;
127         String primaryToolbarButtonText;
128         boolean primaryToolbarButtonEnabled;
129 
130         boolean showSecondaryToolbarButton;
131         String secondaryToolbarButtonText;
132         boolean secondaryToolbarButtonEnabled;
133 
134         boolean showProgressBar;
135         boolean indeterminateProgressBar;
136 
137         try {
138             showBackButton = attrArray.getBoolean(
139                     R.styleable.CarSetupWizardBaseLayout_showBackButton, true);
140             showToolbarTitle = attrArray.getBoolean(
141                     R.styleable.CarSetupWizardBaseLayout_showToolbarTitle, false);
142             toolbarTitleText = attrArray.getString(
143                     R.styleable.CarSetupWizardBaseLayout_toolbarTitleText);
144             showPrimaryToolbarButton = attrArray.getBoolean(
145                     R.styleable.CarSetupWizardBaseLayout_showPrimaryToolbarButton, true);
146             primaryToolbarButtonText = attrArray.getString(
147                     R.styleable.CarSetupWizardBaseLayout_primaryToolbarButtonText);
148             primaryToolbarButtonEnabled = attrArray.getBoolean(
149                     R.styleable.CarSetupWizardBaseLayout_primaryToolbarButtonEnabled, true);
150             mPrimaryToolbarButtonFlat = attrArray.getBoolean(
151                     R.styleable.CarSetupWizardBaseLayout_primaryToolbarButtonFlat, false);
152             showSecondaryToolbarButton = attrArray.getBoolean(
153                     R.styleable.CarSetupWizardBaseLayout_showSecondaryToolbarButton, false);
154             secondaryToolbarButtonText = attrArray.getString(
155                     R.styleable.CarSetupWizardBaseLayout_secondaryToolbarButtonText);
156             secondaryToolbarButtonEnabled = attrArray.getBoolean(
157                     R.styleable.CarSetupWizardBaseLayout_secondaryToolbarButtonEnabled, true);
158             showProgressBar = attrArray.getBoolean(
159                     R.styleable.CarSetupWizardBaseLayout_showProgressBar, false);
160             indeterminateProgressBar = attrArray.getBoolean(
161                     R.styleable.CarSetupWizardBaseLayout_indeterminateProgressBar, true);
162         } finally {
163             attrArray.recycle();
164         }
165 
166         LayoutInflater inflater = LayoutInflater.from(getContext());
167         inflater.inflate(R.layout.car_setup_wizard_layout, this);
168 
169         // Set the back button visibility based on the custom attribute.
170         setBackButton(findViewById(R.id.back_button));
171         Drawable drawable = mPartnerConfigHelper.getDrawable(
172                 getContext(), PartnerConfig.CONFIG_TOOLBAR_BUTTON_ICON_BACK);
173         if (drawable != null) {
174             ((ImageView) mBackButton).setImageDrawable(drawable);
175         }
176         setBackButtonVisible(showBackButton);
177 
178         // Se the title bar.
179         setTitleBar(findViewById(R.id.application_bar));
180         mTitleBarElevation =
181                 getContext().getResources().getDimension(R.dimen.title_bar_drop_shadow_elevation);
182         int toolbarBgColor =
183                 mPartnerConfigHelper.getColor(getContext(), PartnerConfig.CONFIG_TOOLBAR_BG_COLOR);
184         if (toolbarBgColor != 0) {
185             mTitleBar.setBackgroundColor(toolbarBgColor);
186         }
187 
188         // Set the toolbar title visibility and text based on the custom attributes.
189         setToolbarTitle(findViewById(R.id.toolbar_title));
190         if (showToolbarTitle) {
191             setToolbarTitleText(toolbarTitleText);
192         } else {
193             setToolbarTitleVisible(false);
194         }
195 
196         // Set the primary continue button visibility and text based on the custom attributes.
197         ViewStub primaryToolbarButtonStub =
198                 (ViewStub) findViewById(R.id.primary_toolbar_button_stub);
199         // Set the button layout to flat if that attribute was set.
200         if (mPrimaryToolbarButtonFlat) {
201             primaryToolbarButtonStub.setLayoutResource(R.layout.flat_button);
202         }
203         primaryToolbarButtonStub.inflate();
204         setPrimaryToolbarButton(findViewById(R.id.primary_toolbar_button));
205         if (showPrimaryToolbarButton) {
206             setPrimaryToolbarButtonText(primaryToolbarButtonText);
207             setPrimaryToolbarButtonEnabled(primaryToolbarButtonEnabled);
208             stylePrimaryToolbarButton(mPrimaryToolbarButton);
209         } else {
210             setPrimaryToolbarButtonVisible(false);
211         }
212 
213         // Set the secondary continue button visibility and text based on the custom attributes.
214         ViewStub secondaryToolbarButtonStub =
215                 (ViewStub) findViewById(R.id.secondary_toolbar_button_stub);
216         if (showSecondaryToolbarButton || !TextUtils.isEmpty(secondaryToolbarButtonText)) {
217             secondaryToolbarButtonStub.inflate();
218             mSecondaryToolbarButton = findViewById(R.id.secondary_toolbar_button);
219             setSecondaryToolbarButtonText(secondaryToolbarButtonText);
220             setSecondaryToolbarButtonEnabled(secondaryToolbarButtonEnabled);
221             setSecondaryToolbarButtonVisible(showSecondaryToolbarButton);
222         }
223 
224         mProgressBar = findViewById(R.id.progress_bar);
225         setProgressBarVisible(showProgressBar);
226         setProgressBarIndeterminate(indeterminateProgressBar);
227 
228         // Set orientation programmatically since the inflated layout uses <merge>
229         setOrientation(LinearLayout.VERTICAL);
230     }
231 
232     /**
233      * Set a given view's visibility.
234      */
235     @VisibleForTesting
setViewVisible(View view, boolean visible)236     void setViewVisible(View view, boolean visible) {
237         view.setVisibility(visible ? View.VISIBLE : View.GONE);
238     }
239 
240     // Add or remove the back button touch delegate depending on whether it is visible.
241     @VisibleForTesting
updateBackButtonTouchDelegate(boolean visible)242     void updateBackButtonTouchDelegate(boolean visible) {
243         if (visible) {
244             // Post this action in the parent's message queue to make sure the parent
245             // lays out its children before getHitRect() is called
246             this.post(() -> {
247                 Rect delegateArea = new Rect();
248 
249                 mBackButton.getHitRect(delegateArea);
250 
251                 /*
252                  * Update the delegate area based on the difference between the current size and
253                  * the touch target size
254                  */
255                 float touchTargetSize = getResources().getDimension(
256                         R.dimen.car_touch_target_size);
257                 float primaryIconSize = getResources().getDimension(
258                         R.dimen.car_primary_icon_size);
259 
260                 int sizeDifference = (int) ((touchTargetSize - primaryIconSize) / 2);
261 
262                 delegateArea.right += sizeDifference;
263                 delegateArea.bottom += sizeDifference;
264                 delegateArea.left -= sizeDifference;
265                 delegateArea.top -= sizeDifference;
266 
267                 // Set the TouchDelegate on the parent view
268                 TouchDelegate touchDelegate = new TouchDelegate(delegateArea,
269                         mBackButton);
270 
271                 if (View.class.isInstance(mBackButton.getParent())) {
272                     ((View) mBackButton.getParent()).setTouchDelegate(touchDelegate);
273                 }
274             });
275         } else {
276             // Set the TouchDelegate to null if the back button is not visible.
277             if (View.class.isInstance(mBackButton.getParent())) {
278                 ((View) mBackButton.getParent()).setTouchDelegate(null);
279             }
280         }
281     }
282 
283     /**
284      * Gets the back button.
285      */
getBackButton()286     public View getBackButton() {
287         return mBackButton;
288     }
289 
290     @VisibleForTesting
setBackButton(View backButton)291     final void setBackButton(View backButton) {
292         mBackButton = backButton;
293     }
294 
295     /**
296      * Set the back button onClickListener to given listener. Can be null if the listener should
297      * be overridden so no callback is made.
298      */
setBackButtonListener(@ullable View.OnClickListener listener)299     public void setBackButtonListener(@Nullable View.OnClickListener listener) {
300         mBackButton.setOnClickListener(listener);
301     }
302 
303     /**
304      * Set the back button visibility to the given visibility.
305      */
setBackButtonVisible(boolean visible)306     public void setBackButtonVisible(boolean visible) {
307         setViewVisible(mBackButton, visible);
308         updateBackButtonTouchDelegate(visible);
309     }
310 
311     /**
312      * Gets the toolbar title.
313      */
getToolbarTitle()314     public TextView getToolbarTitle() {
315         return mToolbarTitle;
316     }
317 
318     @VisibleForTesting
setToolbarTitle(TextView toolbarTitle)319     final void setToolbarTitle(TextView toolbarTitle) {
320         mToolbarTitle = toolbarTitle;
321     }
322 
323     /**
324      * Sets the header title visibility to given value.
325      */
setToolbarTitleVisible(boolean visible)326     public void setToolbarTitleVisible(boolean visible) {
327         setViewVisible(mToolbarTitle, visible);
328     }
329 
330     /**
331      * Sets the header title text to the provided text.
332      */
setToolbarTitleText(String text)333     public void setToolbarTitleText(String text) {
334         mToolbarTitle.setText(text);
335     }
336 
337     /**
338      * Sets the style for the toolbar title.
339      */
setToolbarTitleStyle(@tyleRes int style)340     public void setToolbarTitleStyle(@StyleRes int style) {
341         mToolbarTitle.setTextAppearance(style);
342     }
343 
344     /**
345      * Gets the primary toolbar button.
346      */
getPrimaryToolbarButton()347     public Button getPrimaryToolbarButton() {
348         return mPrimaryToolbarButton;
349     }
350 
351     @VisibleForTesting
setPrimaryToolbarButton(Button primaryToolbarButton)352     final void setPrimaryToolbarButton(Button primaryToolbarButton) {
353         mPrimaryToolbarButton = primaryToolbarButton;
354     }
355 
356     /**
357      * Set the primary continue button visibility to the given visibility.
358      */
setPrimaryToolbarButtonVisible(boolean visible)359     public void setPrimaryToolbarButtonVisible(boolean visible) {
360         setViewVisible(mPrimaryToolbarButton, visible);
361     }
362 
363     /**
364      * Set whether the primary continue button is enabled.
365      */
setPrimaryToolbarButtonEnabled(boolean enabled)366     public void setPrimaryToolbarButtonEnabled(boolean enabled) {
367         mPrimaryToolbarButton.setEnabled(enabled);
368     }
369 
370     /**
371      * Set the primary continue button text to the given text.
372      */
setPrimaryToolbarButtonText(String text)373     public void setPrimaryToolbarButtonText(String text) {
374         mPrimaryToolbarButton.setText(text);
375     }
376 
377     /**
378      * Set the primary continue button onClickListener to the given listener. Can be null if the
379      * listener should be overridden so no callback is made. All changes to primary toolbar
380      * button's onClickListener should be made here so they can be stored through changes to the
381      * button.
382      */
setPrimaryToolbarButtonListener(@ullable View.OnClickListener listener)383     public void setPrimaryToolbarButtonListener(@Nullable View.OnClickListener listener) {
384         mPrimaryToolbarButtonOnClick = listener;
385         mPrimaryToolbarButton.setOnClickListener(listener);
386     }
387 
388     /**
389      * Getter for the flatness of the primary toolbar button.
390      */
getPrimaryToolbarButtonFlat()391     public boolean getPrimaryToolbarButtonFlat() {
392         return mPrimaryToolbarButtonFlat;
393     }
394 
395     /**
396      * Changes the button in the primary slot to a flat theme, maintaining the text, visibility,
397      * whether it is enabled, and id.
398      * <p>NOTE: that other attributes set manually on the primaryToolbarButton will be lost on calls
399      * to this method as the button will be replaced.</p>
400      */
setPrimaryToolbarButtonFlat(boolean isFlat)401     public void setPrimaryToolbarButtonFlat(boolean isFlat) {
402         // Do nothing if the state isn't changing.
403         if (isFlat == mPrimaryToolbarButtonFlat) {
404             return;
405         }
406         mPrimaryToolbarButtonFlat = isFlat;
407         Button newPrimaryButton = createPrimaryToolbarButton(isFlat);
408 
409         ViewGroup parent = (ViewGroup) findViewById(R.id.button_container);
410         int buttonIndex = parent.indexOfChild(mPrimaryToolbarButton);
411         parent.removeViewAt(buttonIndex);
412         parent.addView(newPrimaryButton, buttonIndex);
413 
414         // Update state of layout
415         setPrimaryToolbarButton(newPrimaryButton);
416     }
417 
418     @VisibleForTesting
createPrimaryToolbarButton(boolean isFlat)419     Button createPrimaryToolbarButton(boolean isFlat) {
420         int layoutId = isFlat ? R.layout.flat_button : R.layout.primary_button;
421         Button newPrimaryButton = (Button) inflate(getContext(), layoutId, null);
422         newPrimaryButton.setId(mPrimaryToolbarButton.getId());
423         newPrimaryButton.setVisibility(mPrimaryToolbarButton.getVisibility());
424         newPrimaryButton.setEnabled(mPrimaryToolbarButton.isEnabled());
425         newPrimaryButton.setText(mPrimaryToolbarButton.getText());
426         newPrimaryButton.setOnClickListener(mPrimaryToolbarButtonOnClick);
427         newPrimaryButton.setLayoutParams(mPrimaryToolbarButton.getLayoutParams());
428         stylePrimaryToolbarButton(newPrimaryButton);
429 
430         return newPrimaryButton;
431     }
432 
433     /**
434      * Gets the secondary toolbar button.
435      */
getSecondaryToolbarButton()436     public Button getSecondaryToolbarButton() {
437         return mSecondaryToolbarButton;
438     }
439 
440     /**
441      * Set the secondary continue button visibility to the given visibility.
442      */
setSecondaryToolbarButtonVisible(boolean visible)443     public void setSecondaryToolbarButtonVisible(boolean visible) {
444         // If not setting it visible and it hasn't been inflated yet then don't inflate.
445         if (!visible && mSecondaryToolbarButton == null) {
446             return;
447         }
448         maybeInflateSecondaryToolbarButton();
449         setViewVisible(mSecondaryToolbarButton, visible);
450     }
451 
452     /**
453      * Sets whether the secondary continue button is enabled.
454      */
setSecondaryToolbarButtonEnabled(boolean enabled)455     public void setSecondaryToolbarButtonEnabled(boolean enabled) {
456         maybeInflateSecondaryToolbarButton();
457         mSecondaryToolbarButton.setEnabled(enabled);
458     }
459 
460     /**
461      * Sets the secondary continue button text to the given text.
462      */
setSecondaryToolbarButtonText(String text)463     public void setSecondaryToolbarButtonText(String text) {
464         maybeInflateSecondaryToolbarButton();
465         mSecondaryToolbarButton.setText(text);
466     }
467 
468     /**
469      * Sets the secondary continue button onClickListener to the given listener. Can be null if the
470      * listener should be overridden so no callback is made.
471      */
setSecondaryToolbarButtonListener(@ullable View.OnClickListener listener)472     public void setSecondaryToolbarButtonListener(@Nullable View.OnClickListener listener) {
473         maybeInflateSecondaryToolbarButton();
474         mSecondaryToolbarButton.setOnClickListener(listener);
475     }
476 
477     /**
478      * Gets the progress bar.
479      */
getProgressBar()480     public ProgressBar getProgressBar() {
481         return mProgressBar;
482     }
483 
484     /**
485      * Sets the progress bar visibility to the given visibility.
486      */
setProgressBarVisible(boolean visible)487     public void setProgressBarVisible(boolean visible) {
488         setViewVisible(mProgressBar, visible);
489     }
490 
491     /**
492      * Sets the progress bar indeterminate/determinate state.
493      */
setProgressBarIndeterminate(boolean indeterminate)494     public void setProgressBarIndeterminate(boolean indeterminate) {
495         mProgressBar.setIndeterminate(indeterminate);
496     }
497 
498     /**
499      * Sets the progress bar's progress.
500      */
setProgressBarProgress(int progress)501     public void setProgressBarProgress(int progress) {
502         setProgressBarIndeterminate(false);
503         mProgressBar.setProgress(progress);
504     }
505 
506     /**
507      * Sets the locale to be used for rendering.
508      */
applyLocale(Locale locale)509     public void applyLocale(Locale locale) {
510         if (locale == null) {
511             return;
512         }
513         int direction = TextUtils.getLayoutDirectionFromLocale(locale);
514         setLayoutDirection(direction);
515 
516         mToolbarTitle.setTextLocale(locale);
517         mToolbarTitle.setLayoutDirection(direction);
518 
519         mPrimaryToolbarButton.setTextLocale(locale);
520         mPrimaryToolbarButton.setLayoutDirection(direction);
521 
522         mSecondaryToolbarButton.setTextLocale(locale);
523         mSecondaryToolbarButton.setLayoutDirection(direction);
524     }
525 
526     /**
527      * Adds elevation to the title bar in order to produce a drop shadow. An animation can be used
528      * in cases where a direct elevation changes would be too jarring.
529      *
530      * @param animate True when a smooth animation is wanted for the adding of the elevation.
531      */
addElevationToTitleBar(boolean animate)532     public void addElevationToTitleBar(boolean animate) {
533         if (animate) {
534             ValueAnimator elevationAnimator =
535                     ValueAnimator.ofFloat(mTitleBar.getElevation(), mTitleBarElevation);
536             elevationAnimator
537                     .setDuration(ANIMATION_DURATION_MS)
538                     .addUpdateListener(
539                             animation -> mTitleBar.setElevation(
540                                     (float) animation.getAnimatedValue()));
541             elevationAnimator.start();
542         } else {
543             mTitleBar.setElevation(mTitleBarElevation);
544         }
545     }
546 
547     /**
548      * Removes the elevation from the title bar, an animation can be used in cases where a direct
549      * elevation changes would be too jarring.
550      *
551      * @param animate True when a smooth animation is wanted for the removal of the elevation.
552      */
removeElevationFromTitleBar(boolean animate)553     public void removeElevationFromTitleBar(boolean animate) {
554         if (animate) {
555             ValueAnimator elevationAnimator =
556                     ValueAnimator.ofFloat(mTitleBar.getElevation(), 0f);
557             elevationAnimator
558                     .setDuration(ANIMATION_DURATION_MS)
559                     .addUpdateListener(
560                             animation -> mTitleBar.setElevation(
561                                     (float) animation.getAnimatedValue()));
562             elevationAnimator.start();
563         } else {
564             mTitleBar.setElevation(0f);
565         }
566     }
567 
568     /**
569      * Sets the title bar view.
570      */
setTitleBar(View titleBar)571     private void setTitleBar(View titleBar) {
572         mTitleBar = titleBar;
573     }
574 
575     /**
576      * A method that inflates the SecondaryToolbarButton if it is has not already been
577      * inflated. If it has been inflated already this method will do nothing.
578      */
maybeInflateSecondaryToolbarButton()579     private void maybeInflateSecondaryToolbarButton() {
580         ViewStub secondaryToolbarButtonStub = findViewById(R.id.secondary_toolbar_button_stub);
581         // If the secondaryToolbarButtonStub is null then the stub has been inflated so there is
582         // nothing to do.
583         if (secondaryToolbarButtonStub != null) {
584             secondaryToolbarButtonStub.inflate();
585             mSecondaryToolbarButton = findViewById(R.id.secondary_toolbar_button);
586             setSecondaryToolbarButtonVisible(false);
587 
588             setBackground(
589                     mSecondaryToolbarButton,
590                     PartnerConfig.CONFIG_TOOLBAR_SECONDARY_BUTTON_BG,
591                     PartnerConfig.CONFIG_TOOLBAR_SECONDARY_BUTTON_BG_COLOR);
592 
593             setButtonPadding(mSecondaryToolbarButton);
594             setButtonTypeFace(mSecondaryToolbarButton);
595             setButtonTextSize(mSecondaryToolbarButton);
596             setButtonTextColor(
597                     mSecondaryToolbarButton,
598                     PartnerConfig.CONFIG_TOOLBAR_SECONDARY_BUTTON_TEXT_COLOR);
599 
600             // Set button spacing
601             float marginEnd = PartnerConfigHelper.get(getContext()).getDimension(
602                     getContext(),
603                     PartnerConfig.CONFIG_TOOLBAR_BUTTON_SPACING);
604 
605             MarginLayoutParams layoutParams =
606                     (MarginLayoutParams) mSecondaryToolbarButton.getLayoutParams();
607             layoutParams.setMarginEnd(Math.round(marginEnd));
608         }
609     }
610 
611     /** Sets button text color using partner overlay if exists */
612     @VisibleForTesting
setButtonTextColor(TextView button, PartnerConfig config)613     void setButtonTextColor(TextView button, PartnerConfig config) {
614         int color = mPartnerConfigHelper.getColor(getContext(), config);
615         if (color != 0) {
616             button.setTextColor(color);
617         }
618     }
619 
620     /**
621      * Sets background using partner overlay if exists. Background color and radius are only
622      * applied if background resource doesn't exist. Otherwise default background color and radius
623      * may override what's set in the background.
624      */
625     @VisibleForTesting
setBackground(View view, PartnerConfig bgConfig, PartnerConfig bgColorConfig)626     void setBackground(View view, PartnerConfig bgConfig, PartnerConfig bgColorConfig) {
627         Drawable background = mPartnerConfigHelper.getDrawable(getContext(), bgConfig);
628         if (background == null) {
629             if (view instanceof Button) {
630                 setButtonRadius((Button) view);
631             }
632             setBackgroundColor(view, bgColorConfig);
633         } else {
634             view.setBackground(background);
635         }
636     }
637 
638     /** Sets button background color using partner overlay if exists */
639     @VisibleForTesting
setBackgroundColor(View button, PartnerConfig config)640     void setBackgroundColor(View button, PartnerConfig config) {
641         int color = mPartnerConfigHelper.getColor(getContext(), config);
642         if (color != 0) {
643             Drawable background = button.getBackground();
644             if (background != null) {
645                 background.mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
646             }
647         }
648     }
649 
650     /** Sets button text size using partner overlay if exists */
651     @VisibleForTesting
setButtonTextSize(TextView button)652     void setButtonTextSize(TextView button) {
653         float dimension = mPartnerConfigHelper.getDimension(
654                 getContext(),
655                 PartnerConfig.CONFIG_TOOLBAR_BUTTON_TEXT_SIZE);
656         if (dimension != 0) {
657             button.setTextSize(TypedValue.COMPLEX_UNIT_PX, dimension);
658         }
659     }
660 
661     /** Sets button type face with partner overlay if exists */
setButtonTypeFace(TextView button)662     private void setButtonTypeFace(TextView button) {
663         String fontFamily = mPartnerConfigHelper.getString(
664                 getContext(),
665                 PartnerConfig.CONFIG_TOOLBAR_BUTTON_FONT_FAMILY);
666         if (TextUtils.isEmpty(fontFamily)) {
667             return;
668         }
669 
670         Typeface typeface = Typeface.create(fontFamily, Typeface.NORMAL);
671         if (Objects.equals(typeface, Typeface.DEFAULT)) {
672             Log.w(TAG, String.format(
673                     "Couldn't find font: %s. Setting default font.",
674                     fontFamily));
675         }
676         button.setTypeface(typeface);
677     }
678 
679     /** Sets button radius using partner overlay if exists */
setButtonRadius(Button button)680     private void setButtonRadius(Button button) {
681         float radius = mPartnerConfigHelper.getDimension(
682                 getContext(),
683                 PartnerConfig.CONFIG_TOOLBAR_BUTTON_RADIUS);
684 
685         GradientDrawable gradientDrawable = getGradientDrawable(button);
686         if (gradientDrawable != null) {
687             gradientDrawable.setCornerRadius(radius);
688         }
689     }
690 
setButtonPadding(Button button)691     private void setButtonPadding(Button button) {
692         int hPadding = Math.round(
693                 PartnerConfigHelper.get(getContext()).getDimension(
694                         getContext(),
695                         PartnerConfig.CONFIG_TOOLBAR_BUTTON_PADDING_HORIZONTAL)
696         );
697         int vPadding = Math.round(
698                 PartnerConfigHelper.get(getContext()).getDimension(
699                         getContext(),
700                         PartnerConfig.CONFIG_TOOLBAR_BUTTON_PADDING_VERTICAL)
701         );
702         button.setPadding(hPadding, vPadding, hPadding, vPadding);
703     }
704 
stylePrimaryToolbarButton(Button primaryButton)705     private void stylePrimaryToolbarButton(Button primaryButton) {
706         if (!mPrimaryToolbarButtonFlat) {
707             setBackground(
708                     primaryButton,
709                     PartnerConfig.CONFIG_TOOLBAR_PRIMARY_BUTTON_BG,
710                     PartnerConfig.CONFIG_TOOLBAR_PRIMARY_BUTTON_BG_COLOR);
711         }
712 
713         setButtonPadding(primaryButton);
714         setButtonTypeFace(primaryButton);
715         setButtonTextSize(primaryButton);
716 
717         PartnerConfig textColorConfig = mPrimaryToolbarButtonFlat
718                 ? PartnerConfig.CONFIG_TOOLBAR_SECONDARY_BUTTON_TEXT_COLOR
719                 : PartnerConfig.CONFIG_TOOLBAR_PRIMARY_BUTTON_TEXT_COLOR;
720         setButtonTextColor(primaryButton, textColorConfig);
721     }
722 
getGradientDrawable(Button button)723     private GradientDrawable getGradientDrawable(Button button) {
724         Drawable drawable = button.getBackground();
725         if (drawable instanceof InsetDrawable) {
726             return getGradientDrawableFromInsetDrawable((InsetDrawable) drawable);
727         }
728 
729         if (drawable instanceof RippleDrawable) {
730             drawable = ((RippleDrawable) drawable).getDrawable(0);
731             if (drawable instanceof InsetDrawable) {
732                 return getGradientDrawableFromInsetDrawable((InsetDrawable) drawable);
733             }
734             return (GradientDrawable) drawable;
735         }
736 
737         return null;
738     }
739 
getGradientDrawableFromInsetDrawable(InsetDrawable insetDrawable)740     private GradientDrawable getGradientDrawableFromInsetDrawable(InsetDrawable insetDrawable) {
741         return (GradientDrawable) insetDrawable.getDrawable();
742     }
743 }
744