| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.android.setupcompat.template; |
| |
| import static com.google.android.setupcompat.internal.Preconditions.ensureOnMainThread; |
| |
| import android.annotation.SuppressLint; |
| import android.annotation.TargetApi; |
| import android.content.Context; |
| import android.content.res.ColorStateList; |
| import android.content.res.TypedArray; |
| import android.graphics.Color; |
| import android.os.Build; |
| import android.os.Build.VERSION_CODES; |
| import android.os.PersistableBundle; |
| import android.util.AttributeSet; |
| import android.view.ContextThemeWrapper; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewStub; |
| import android.widget.Button; |
| import android.widget.LinearLayout; |
| import android.widget.LinearLayout.LayoutParams; |
| import androidx.annotation.AttrRes; |
| import androidx.annotation.CallSuper; |
| import androidx.annotation.ColorInt; |
| import androidx.annotation.IdRes; |
| import androidx.annotation.LayoutRes; |
| import androidx.annotation.MainThread; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.StyleRes; |
| import androidx.annotation.VisibleForTesting; |
| import com.google.android.setupcompat.PartnerCustomizationLayout; |
| import com.google.android.setupcompat.R; |
| import com.google.android.setupcompat.internal.FooterButtonPartnerConfig; |
| import com.google.android.setupcompat.internal.TemplateLayout; |
| import com.google.android.setupcompat.logging.internal.FooterBarMixinMetrics; |
| import com.google.android.setupcompat.partnerconfig.PartnerConfig; |
| import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; |
| import com.google.android.setupcompat.template.FooterButton.ButtonType; |
| import java.util.Locale; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| /** |
| * A {@link Mixin} for managing buttons. By default, the button bar expects that buttons on the |
| * start (left for LTR) are "secondary" borderless buttons, while buttons on the end (right for LTR) |
| * are "primary" accent-colored buttons. |
| */ |
| public class FooterBarMixin implements Mixin { |
| |
| private final Context context; |
| |
| @Nullable private final ViewStub footerStub; |
| |
| @VisibleForTesting final boolean applyPartnerResources; |
| @VisibleForTesting final boolean applyDynamicColor; |
| @VisibleForTesting final boolean useFullDynamicColor; |
| |
| private LinearLayout buttonContainer; |
| private FooterButton primaryButton; |
| private FooterButton secondaryButton; |
| @IdRes private int primaryButtonId; |
| @IdRes private int secondaryButtonId; |
| ColorStateList primaryDefaultTextColor = null; |
| ColorStateList secondaryDefaultTextColor = null; |
| @VisibleForTesting public FooterButtonPartnerConfig primaryButtonPartnerConfigForTesting; |
| @VisibleForTesting public FooterButtonPartnerConfig secondaryButtonPartnerConfigForTesting; |
| |
| private int footerBarPaddingTop; |
| private int footerBarPaddingBottom; |
| @VisibleForTesting int defaultPadding; |
| @ColorInt private final int footerBarPrimaryBackgroundColor; |
| @ColorInt private final int footerBarSecondaryBackgroundColor; |
| private boolean removeFooterBarWhenEmpty = true; |
| private boolean isSecondaryButtonInPrimaryStyle = false; |
| |
| private static final AtomicInteger nextGeneratedId = new AtomicInteger(1); |
| |
| @VisibleForTesting public final FooterBarMixinMetrics metrics = new FooterBarMixinMetrics(); |
| |
| private FooterButton.OnButtonEventListener createButtonEventListener(@IdRes int id) { |
| |
| return new FooterButton.OnButtonEventListener() { |
| |
| @Override |
| public void onEnabledChanged(boolean enabled) { |
| if (buttonContainer != null) { |
| Button button = buttonContainer.findViewById(id); |
| if (button != null) { |
| button.setEnabled(enabled); |
| if (applyPartnerResources) { |
| updateButtonTextColorWithPartnerConfig( |
| button, |
| (id == primaryButtonId || isSecondaryButtonInPrimaryStyle) |
| ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR |
| : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onVisibilityChanged(int visibility) { |
| if (buttonContainer != null) { |
| Button button = buttonContainer.findViewById(id); |
| if (button != null) { |
| button.setVisibility(visibility); |
| autoSetButtonBarVisibility(); |
| } |
| } |
| } |
| |
| @Override |
| public void onTextChanged(CharSequence text) { |
| if (buttonContainer != null) { |
| Button button = buttonContainer.findViewById(id); |
| if (button != null) { |
| button.setText(text); |
| } |
| } |
| } |
| |
| @Override |
| @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) |
| public void onLocaleChanged(Locale locale) { |
| if (buttonContainer != null) { |
| Button button = buttonContainer.findViewById(id); |
| if (button != null && locale != null) { |
| button.setTextLocale(locale); |
| } |
| } |
| } |
| |
| @Override |
| @TargetApi(VERSION_CODES.JELLY_BEAN_MR1) |
| public void onDirectionChanged(int direction) { |
| if (buttonContainer != null && direction != -1) { |
| buttonContainer.setLayoutDirection(direction); |
| } |
| } |
| }; |
| } |
| |
| /** |
| * Creates a mixin for managing buttons on the footer. |
| * |
| * @param layout The {@link TemplateLayout} containing this mixin. |
| * @param attrs XML attributes given to the layout. |
| * @param defStyleAttr The default style attribute as given to the constructor of the layout. |
| */ |
| public FooterBarMixin( |
| TemplateLayout layout, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { |
| context = layout.getContext(); |
| footerStub = layout.findManagedViewById(R.id.suc_layout_footer); |
| this.applyPartnerResources = |
| layout instanceof PartnerCustomizationLayout |
| && ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource(); |
| |
| applyDynamicColor = |
| layout instanceof PartnerCustomizationLayout |
| && ((PartnerCustomizationLayout) layout).shouldApplyDynamicColor(); |
| |
| useFullDynamicColor = |
| layout instanceof PartnerCustomizationLayout |
| && ((PartnerCustomizationLayout) layout).useFullDynamicColor(); |
| |
| TypedArray a = |
| context.obtainStyledAttributes(attrs, R.styleable.SucFooterBarMixin, defStyleAttr, 0); |
| defaultPadding = |
| a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarPaddingVertical, 0); |
| footerBarPaddingTop = |
| a.getDimensionPixelSize( |
| R.styleable.SucFooterBarMixin_sucFooterBarPaddingTop, defaultPadding); |
| footerBarPaddingBottom = |
| a.getDimensionPixelSize( |
| R.styleable.SucFooterBarMixin_sucFooterBarPaddingBottom, defaultPadding); |
| footerBarPrimaryBackgroundColor = |
| a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); |
| footerBarSecondaryBackgroundColor = |
| a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterBackground, 0); |
| |
| int primaryBtn = |
| a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButton, 0); |
| int secondaryBtn = |
| a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterButton, 0); |
| a.recycle(); |
| |
| FooterButtonInflater inflater = new FooterButtonInflater(context); |
| |
| if (secondaryBtn != 0) { |
| setSecondaryButton(inflater.inflate(secondaryBtn)); |
| metrics.logPrimaryButtonInitialStateVisibility(/* isVisible= */ true, /* isUsingXml= */ true); |
| } |
| |
| if (primaryBtn != 0) { |
| setPrimaryButton(inflater.inflate(primaryBtn)); |
| metrics.logSecondaryButtonInitialStateVisibility( |
| /* isVisible= */ true, /* isUsingXml= */ true); |
| } |
| } |
| |
| private View addSpace() { |
| LinearLayout buttonContainer = ensureFooterInflated(); |
| View space = new View(buttonContainer.getContext()); |
| space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); |
| space.setVisibility(View.INVISIBLE); |
| buttonContainer.addView(space); |
| return space; |
| } |
| |
| @NonNull |
| private LinearLayout ensureFooterInflated() { |
| if (buttonContainer == null) { |
| if (footerStub == null) { |
| throw new IllegalStateException("Footer stub is not found in this template"); |
| } |
| buttonContainer = (LinearLayout) inflateFooter(R.layout.suc_footer_button_bar); |
| onFooterBarInflated(buttonContainer); |
| onFooterBarApplyPartnerResource(buttonContainer); |
| } |
| return buttonContainer; |
| } |
| |
| /** |
| * Notifies that the footer bar has been inflated to the view hierarchy. Calling super is |
| * necessary while subclass implement it. |
| */ |
| @CallSuper |
| protected void onFooterBarInflated(LinearLayout buttonContainer) { |
| if (buttonContainer == null) { |
| // Ignore action since buttonContainer is null |
| return; |
| } |
| if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { |
| buttonContainer.setId(View.generateViewId()); |
| } else { |
| buttonContainer.setId(generateViewId()); |
| } |
| updateFooterBarPadding( |
| buttonContainer, |
| buttonContainer.getPaddingLeft(), |
| footerBarPaddingTop, |
| buttonContainer.getPaddingRight(), |
| footerBarPaddingBottom); |
| } |
| |
| /** |
| * Notifies while the footer bar apply Partner Resource. Calling super is necessary while subclass |
| * implement it. |
| */ |
| @CallSuper |
| protected void onFooterBarApplyPartnerResource(LinearLayout buttonContainer) { |
| if (buttonContainer == null) { |
| // Ignore action since buttonContainer is null |
| return; |
| } |
| if (!applyPartnerResources) { |
| return; |
| } |
| |
| // skip apply partner resources on footerbar background if dynamic color enabled |
| if (!useFullDynamicColor) { |
| @ColorInt |
| int color = |
| PartnerConfigHelper.get(context) |
| .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR); |
| buttonContainer.setBackgroundColor(color); |
| } |
| |
| footerBarPaddingTop = |
| (int) |
| PartnerConfigHelper.get(context) |
| .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP); |
| footerBarPaddingBottom = |
| (int) |
| PartnerConfigHelper.get(context) |
| .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); |
| updateFooterBarPadding( |
| buttonContainer, |
| buttonContainer.getPaddingLeft(), |
| footerBarPaddingTop, |
| buttonContainer.getPaddingRight(), |
| footerBarPaddingBottom); |
| |
| if (PartnerConfigHelper.get(context) |
| .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT)) { |
| int minHeight = |
| (int) |
| PartnerConfigHelper.get(context) |
| .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT); |
| if (minHeight > 0) { |
| buttonContainer.setMinimumHeight(minHeight); |
| } |
| } |
| } |
| |
| /** |
| * Inflate FooterActionButton with layout "suc_button". Subclasses can implement this method to |
| * modify the footer button layout as necessary. |
| */ |
| @SuppressLint("InflateParams") |
| protected FooterActionButton createThemedButton(Context context, @StyleRes int theme) { |
| // Inflate a single button from XML, which when using support lib, will take advantage of |
| // the injected layout inflater and give us AppCompatButton instead. |
| LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme)); |
| return (FooterActionButton) inflater.inflate(R.layout.suc_button, null, false); |
| } |
| |
| /** Sets primary button for footer. */ |
| @MainThread |
| public void setPrimaryButton(FooterButton footerButton) { |
| ensureOnMainThread("setPrimaryButton"); |
| ensureFooterInflated(); |
| |
| // Setup button partner config |
| FooterButtonPartnerConfig footerButtonPartnerConfig = |
| new FooterButtonPartnerConfig.Builder(footerButton) |
| .setPartnerTheme( |
| getPartnerTheme( |
| footerButton, |
| /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Primary, |
| /* buttonBackgroundColorConfig= */ PartnerConfig |
| .CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)) |
| .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) |
| .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) |
| .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) |
| .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) |
| .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) |
| .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) |
| .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR) |
| .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) |
| .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) |
| .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) |
| .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE) |
| .build(); |
| |
| FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); |
| // update information for primary button. Need to update as long as the button inflated. |
| primaryButtonId = button.getId(); |
| primaryDefaultTextColor = button.getTextColors(); |
| primaryButton = footerButton; |
| primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; |
| |
| onFooterButtonInflated(button, footerBarPrimaryBackgroundColor); |
| onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); |
| |
| // Make sure the position of buttons are correctly and prevent primary button create twice or |
| // more. |
| repopulateButtons(); |
| } |
| |
| /** Returns the {@link FooterButton} of primary button. */ |
| public FooterButton getPrimaryButton() { |
| return primaryButton; |
| } |
| |
| @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) |
| public Button getPrimaryButtonView() { |
| return buttonContainer == null ? null : buttonContainer.findViewById(primaryButtonId); |
| } |
| |
| @VisibleForTesting |
| boolean isPrimaryButtonVisible() { |
| return getPrimaryButtonView() != null && getPrimaryButtonView().getVisibility() == View.VISIBLE; |
| } |
| |
| /** Sets secondary button for footer. */ |
| @MainThread |
| public void setSecondaryButton(FooterButton footerButton) { |
| setSecondaryButton(footerButton, /*usePrimaryStyle= */ false); |
| } |
| |
| /** Sets secondary button for footer. Allow to use the primary button style. */ |
| @MainThread |
| public void setSecondaryButton(FooterButton footerButton, boolean usePrimaryStyle) { |
| ensureOnMainThread("setSecondaryButton"); |
| isSecondaryButtonInPrimaryStyle = usePrimaryStyle; |
| ensureFooterInflated(); |
| |
| // Setup button partner config |
| FooterButtonPartnerConfig footerButtonPartnerConfig = |
| new FooterButtonPartnerConfig.Builder(footerButton) |
| .setPartnerTheme( |
| getPartnerTheme( |
| footerButton, |
| /* defaultPartnerTheme= */ usePrimaryStyle |
| ? R.style.SucPartnerCustomizationButton_Primary |
| : R.style.SucPartnerCustomizationButton_Secondary, |
| /* buttonBackgroundColorConfig= */ usePrimaryStyle |
| ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR |
| : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)) |
| .setButtonBackgroundConfig( |
| usePrimaryStyle |
| ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR |
| : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) |
| .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) |
| .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) |
| .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) |
| .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) |
| .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) |
| .setTextColorConfig( |
| usePrimaryStyle |
| ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR |
| : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) |
| .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) |
| .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) |
| .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) |
| .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE) |
| .build(); |
| |
| FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig); |
| // update information for secondary button. Need to update as long as the button inflated. |
| secondaryButtonId = button.getId(); |
| secondaryDefaultTextColor = button.getTextColors(); |
| secondaryButton = footerButton; |
| secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; |
| |
| onFooterButtonInflated(button, footerBarSecondaryBackgroundColor); |
| onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); |
| |
| // Make sure the position of buttons are correctly and prevent secondary button create twice or |
| // more. |
| repopulateButtons(); |
| } |
| |
| /** |
| * Corrects the order of footer buttons after the button has been inflated to the view hierarchy. |
| * Subclasses can implement this method to modify the order of footer buttons as necessary. |
| */ |
| protected void repopulateButtons() { |
| LinearLayout buttonContainer = ensureFooterInflated(); |
| Button tempPrimaryButton = getPrimaryButtonView(); |
| Button tempSecondaryButton = getSecondaryButtonView(); |
| buttonContainer.removeAllViews(); |
| |
| if (tempSecondaryButton != null) { |
| if (isSecondaryButtonInPrimaryStyle) { |
| // Since the secondary button has the same style (with background) as the primary button, |
| // we need to have the left padding equal to the right padding. |
| updateFooterBarPadding( |
| buttonContainer, |
| buttonContainer.getPaddingRight(), |
| buttonContainer.getPaddingTop(), |
| buttonContainer.getPaddingRight(), |
| buttonContainer.getPaddingBottom()); |
| } |
| buttonContainer.addView(tempSecondaryButton); |
| } |
| addSpace(); |
| if (tempPrimaryButton != null) { |
| buttonContainer.addView(tempPrimaryButton); |
| } |
| } |
| |
| /** |
| * Notifies that the footer button has been inInflated and add to the view hierarchy. Calling |
| * super is necessary while subclass implement it. |
| */ |
| @CallSuper |
| protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) { |
| // Try to set default background |
| if (defaultButtonBackgroundColor != 0) { |
| FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor); |
| } else { |
| // TODO: get button background color from activity theme |
| } |
| buttonContainer.addView(button); |
| autoSetButtonBarVisibility(); |
| } |
| |
| private int getPartnerTheme( |
| FooterButton footerButton, |
| int defaultPartnerTheme, |
| PartnerConfig buttonBackgroundColorConfig) { |
| int overrideTheme = footerButton.getTheme(); |
| |
| // Set the default theme if theme is not set, or when running in setup flow. |
| if (footerButton.getTheme() == 0 || applyPartnerResources) { |
| overrideTheme = defaultPartnerTheme; |
| } |
| // TODO: Make sure customize attributes in theme can be applied during setup flow. |
| // If sets background color to full transparent, the button changes to colored borderless ink |
| // button style. |
| if (applyPartnerResources) { |
| int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig); |
| if (color == Color.TRANSPARENT) { |
| overrideTheme = R.style.SucPartnerCustomizationButton_Secondary; |
| } else if (color != Color.TRANSPARENT) { |
| // TODO: remove the constrain (color != Color.WHITE), need to check all pages |
| // go well without customization. It should be fine since the default value of secondary bg |
| // color is set as transparent. |
| overrideTheme = R.style.SucPartnerCustomizationButton_Primary; |
| } |
| } |
| return overrideTheme; |
| } |
| |
| @VisibleForTesting |
| public LinearLayout getButtonContainer() { |
| return buttonContainer; |
| } |
| |
| /** Returns the {@link FooterButton} of secondary button. */ |
| public FooterButton getSecondaryButton() { |
| return secondaryButton; |
| } |
| |
| /** |
| * Sets whether the footer bar should be removed when there are no footer buttons in the bar. |
| * |
| * @param value True if footer bar is gone, false otherwise. |
| */ |
| public void setRemoveFooterBarWhenEmpty(boolean value) { |
| removeFooterBarWhenEmpty = value; |
| autoSetButtonBarVisibility(); |
| } |
| |
| /** |
| * Checks the visibility state of footer buttons to set the visibility state of this footer bar |
| * automatically. |
| */ |
| private void autoSetButtonBarVisibility() { |
| Button primaryButton = getPrimaryButtonView(); |
| Button secondaryButton = getSecondaryButtonView(); |
| boolean primaryVisible = primaryButton != null && primaryButton.getVisibility() == View.VISIBLE; |
| boolean secondaryVisible = |
| secondaryButton != null && secondaryButton.getVisibility() == View.VISIBLE; |
| |
| if (buttonContainer != null) { |
| buttonContainer.setVisibility( |
| primaryVisible || secondaryVisible |
| ? View.VISIBLE |
| : removeFooterBarWhenEmpty ? View.GONE : View.INVISIBLE); |
| } |
| } |
| |
| /** Returns the visibility status for this footer bar. */ |
| @VisibleForTesting |
| public int getVisibility() { |
| return buttonContainer.getVisibility(); |
| } |
| |
| @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED) |
| public Button getSecondaryButtonView() { |
| return buttonContainer == null ? null : buttonContainer.findViewById(secondaryButtonId); |
| } |
| |
| @VisibleForTesting |
| boolean isSecondaryButtonVisible() { |
| return getSecondaryButtonView() != null |
| && getSecondaryButtonView().getVisibility() == View.VISIBLE; |
| } |
| |
| private static int generateViewId() { |
| for (; ; ) { |
| final int result = nextGeneratedId.get(); |
| // aapt-generated IDs have the high byte nonzero; clamp to the range under that. |
| int newValue = result + 1; |
| if (newValue > 0x00FFFFFF) { |
| newValue = 1; // Roll over to 1, not 0. |
| } |
| if (nextGeneratedId.compareAndSet(result, newValue)) { |
| return result; |
| } |
| } |
| } |
| |
| private FooterActionButton inflateButton( |
| FooterButton footerButton, FooterButtonPartnerConfig footerButtonPartnerConfig) { |
| FooterActionButton button = |
| createThemedButton(context, footerButtonPartnerConfig.getPartnerTheme()); |
| if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { |
| button.setId(View.generateViewId()); |
| } else { |
| button.setId(generateViewId()); |
| } |
| |
| // apply initial configuration into button view. |
| button.setText(footerButton.getText()); |
| button.setOnClickListener(footerButton); |
| button.setVisibility(footerButton.getVisibility()); |
| button.setEnabled(footerButton.isEnabled()); |
| button.setFooterButton(footerButton); |
| |
| footerButton.setOnButtonEventListener(createButtonEventListener(button.getId())); |
| return button; |
| } |
| |
| // TODO: Make sure customize attributes in theme can be applied during setup flow. |
| @TargetApi(VERSION_CODES.Q) |
| private void onFooterButtonApplyPartnerResource( |
| Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) { |
| if (!applyPartnerResources) { |
| return; |
| } |
| |
| // If dynamic color enabled, these colors won't be overrode by partner config. |
| // Instead, these colors align with the current theme colors. |
| if (!applyDynamicColor) { |
| updateButtonTextColorWithPartnerConfig( |
| button, footerButtonPartnerConfig.getButtonTextColorConfig()); |
| FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig( |
| context, |
| button, |
| footerButtonPartnerConfig.getButtonBackgroundConfig(), |
| footerButtonPartnerConfig.getButtonDisableAlphaConfig(), |
| footerButtonPartnerConfig.getButtonDisableBackgroundConfig()); |
| FooterButtonStyleUtils.updateButtonRippleColorWithPartnerConfig( |
| context, button, footerButtonPartnerConfig); |
| } |
| |
| FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig( |
| context, button, footerButtonPartnerConfig.getButtonTextSizeConfig()); |
| FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig( |
| context, button, footerButtonPartnerConfig.getButtonMinHeightConfig()); |
| FooterButtonStyleUtils.updateButtonTypeFaceWithPartnerConfig( |
| context, |
| button, |
| footerButtonPartnerConfig.getButtonTextTypeFaceConfig(), |
| footerButtonPartnerConfig.getButtonTextStyleConfig()); |
| FooterButtonStyleUtils.updateButtonRadiusWithPartnerConfig( |
| context, button, footerButtonPartnerConfig.getButtonRadiusConfig()); |
| FooterButtonStyleUtils.updateButtonIconWithPartnerConfig( |
| context, |
| button, |
| footerButtonPartnerConfig.getButtonIconConfig(), |
| primaryButtonId == button.getId()); |
| } |
| |
| private void updateButtonTextColorWithPartnerConfig( |
| Button button, PartnerConfig buttonTextColorConfig) { |
| if (button.isEnabled()) { |
| FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig( |
| context, button, buttonTextColorConfig); |
| } else { |
| FooterButtonStyleUtils.updateButtonTextDisableColor( |
| button, |
| /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle) |
| ? primaryDefaultTextColor |
| : secondaryDefaultTextColor); |
| } |
| } |
| |
| private static PartnerConfig getDrawablePartnerConfig(@ButtonType int buttonType) { |
| PartnerConfig result; |
| switch (buttonType) { |
| case ButtonType.ADD_ANOTHER: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_ADD_ANOTHER; |
| break; |
| case ButtonType.CANCEL: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CANCEL; |
| break; |
| case ButtonType.CLEAR: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_CLEAR; |
| break; |
| case ButtonType.DONE: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_DONE; |
| break; |
| case ButtonType.NEXT: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_NEXT; |
| break; |
| case ButtonType.OPT_IN: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_OPT_IN; |
| break; |
| case ButtonType.SKIP: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_SKIP; |
| break; |
| case ButtonType.STOP: |
| result = PartnerConfig.CONFIG_FOOTER_BUTTON_ICON_STOP; |
| break; |
| case ButtonType.OTHER: |
| default: |
| result = null; |
| break; |
| } |
| return result; |
| } |
| |
| protected View inflateFooter(@LayoutRes int footer) { |
| if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { |
| LayoutInflater inflater = |
| LayoutInflater.from( |
| new ContextThemeWrapper(context, R.style.SucPartnerCustomizationButtonBar_Stackable)); |
| footerStub.setLayoutInflater(inflater); |
| } |
| footerStub.setLayoutResource(footer); |
| return footerStub.inflate(); |
| } |
| |
| private void updateFooterBarPadding( |
| LinearLayout buttonContainer, int left, int top, int right, int bottom) { |
| if (buttonContainer == null) { |
| // Ignore action since buttonContainer is null |
| return; |
| } |
| buttonContainer.setPadding(left, top, right, bottom); |
| } |
| |
| /** Returns the paddingTop of footer bar. */ |
| @VisibleForTesting |
| int getPaddingTop() { |
| return (buttonContainer != null) ? buttonContainer.getPaddingTop() : footerStub.getPaddingTop(); |
| } |
| |
| /** Returns the paddingBottom of footer bar. */ |
| @VisibleForTesting |
| int getPaddingBottom() { |
| return (buttonContainer != null) |
| ? buttonContainer.getPaddingBottom() |
| : footerStub.getPaddingBottom(); |
| } |
| |
| /** Uses for notify mixin the view already attached to window. */ |
| public void onAttachedToWindow() { |
| metrics.logPrimaryButtonInitialStateVisibility( |
| /* isVisible= */ isPrimaryButtonVisible(), /* isUsingXml= */ false); |
| metrics.logSecondaryButtonInitialStateVisibility( |
| /* isVisible= */ isSecondaryButtonVisible(), /* isUsingXml= */ false); |
| } |
| |
| /** Uses for notify mixin the view already detached from window. */ |
| public void onDetachedFromWindow() { |
| metrics.updateButtonVisibility(isPrimaryButtonVisible(), isSecondaryButtonVisible()); |
| } |
| |
| /** |
| * Assigns logging metrics to bundle for PartnerCustomizationLayout to log metrics to SetupWizard. |
| */ |
| @TargetApi(VERSION_CODES.Q) |
| public PersistableBundle getLoggingMetrics() { |
| return metrics.getMetrics(); |
| } |
| } |