diff options
author | Xin Li <delphij@google.com> | 2022-03-08 00:17:46 +0000 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2022-03-08 00:17:46 +0000 |
commit | 524748a371e183f627e2778dbc260433f40ad648 (patch) | |
tree | df52a75f31b849ff68089739cce95e9fb1388ed7 | |
parent | a429fac76295bb3f52007071b8e6482dd46d4490 (diff) | |
parent | 2bac0b43041270f97ea96c3fc544b7f636e4ed37 (diff) | |
download | setupcompat-524748a371e183f627e2778dbc260433f40ad648.tar.gz |
Merge Android 12L
Bug: 222710654
Merged-In: I41b0f264d344daeb60050231160084c448083f16
Change-Id: I9eef041d2c53ec22da3c85cd61df666d9b7c2e49
15 files changed, 726 insertions, 62 deletions
diff --git a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java index 5f8bf67..8e23c1a 100644 --- a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java +++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java @@ -24,8 +24,10 @@ public class FooterButtonPartnerConfig { private final PartnerConfig buttonBackgroundConfig; private final PartnerConfig buttonDisableAlphaConfig; private final PartnerConfig buttonDisableBackgroundConfig; + private final PartnerConfig buttonDisableTextColorConfig; private final PartnerConfig buttonIconConfig; private final PartnerConfig buttonTextColorConfig; + private final PartnerConfig buttonMarginStartConfig; private final PartnerConfig buttonTextSizeConfig; private final PartnerConfig buttonMinHeightConfig; private final PartnerConfig buttonTextTypeFaceConfig; @@ -39,8 +41,10 @@ public class FooterButtonPartnerConfig { PartnerConfig buttonBackgroundConfig, PartnerConfig buttonDisableAlphaConfig, PartnerConfig buttonDisableBackgroundConfig, + PartnerConfig buttonDisableTextColorConfig, PartnerConfig buttonIconConfig, PartnerConfig buttonTextColorConfig, + PartnerConfig buttonMarginStartConfig, PartnerConfig buttonTextSizeConfig, PartnerConfig buttonMinHeightConfig, PartnerConfig buttonTextTypeFaceConfig, @@ -50,6 +54,7 @@ public class FooterButtonPartnerConfig { this.partnerTheme = partnerTheme; this.buttonTextColorConfig = buttonTextColorConfig; + this.buttonMarginStartConfig = buttonMarginStartConfig; this.buttonTextSizeConfig = buttonTextSizeConfig; this.buttonMinHeightConfig = buttonMinHeightConfig; this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig; @@ -57,6 +62,7 @@ public class FooterButtonPartnerConfig { this.buttonBackgroundConfig = buttonBackgroundConfig; this.buttonDisableAlphaConfig = buttonDisableAlphaConfig; this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig; + this.buttonDisableTextColorConfig = buttonDisableTextColorConfig; this.buttonRadiusConfig = buttonRadiusConfig; this.buttonIconConfig = buttonIconConfig; this.buttonRippleColorAlphaConfig = buttonRippleColorAlphaConfig; @@ -78,6 +84,10 @@ public class FooterButtonPartnerConfig { return buttonDisableBackgroundConfig; } + public PartnerConfig getButtonDisableTextColorConfig() { + return buttonDisableTextColorConfig; + } + public PartnerConfig getButtonIconConfig() { return buttonIconConfig; } @@ -86,6 +96,10 @@ public class FooterButtonPartnerConfig { return buttonTextColorConfig; } + public PartnerConfig getButtonMarginStartConfig() { + return buttonMarginStartConfig; + } + public PartnerConfig getButtonMinHeightConfig() { return buttonMinHeightConfig; } @@ -116,8 +130,10 @@ public class FooterButtonPartnerConfig { private PartnerConfig buttonBackgroundConfig = null; private PartnerConfig buttonDisableAlphaConfig = null; private PartnerConfig buttonDisableBackgroundConfig = null; + private PartnerConfig buttonDisableTextColorConfig = null; private PartnerConfig buttonIconConfig = null; private PartnerConfig buttonTextColorConfig = null; + private PartnerConfig buttonMarginStartConfig = null; private PartnerConfig buttonTextSizeConfig = null; private PartnerConfig buttonMinHeight = null; private PartnerConfig buttonTextTypeFaceConfig = null; @@ -149,11 +165,21 @@ public class FooterButtonPartnerConfig { return this; } + public Builder setButtonDisableTextColorConfig(PartnerConfig buttonDisableTextColorConfig) { + this.buttonDisableTextColorConfig = buttonDisableTextColorConfig; + return this; + } + public Builder setButtonIconConfig(PartnerConfig buttonIconConfig) { this.buttonIconConfig = buttonIconConfig; return this; } + public Builder setMarginStartConfig(PartnerConfig buttonMarginStartConfig) { + this.buttonMarginStartConfig = buttonMarginStartConfig; + return this; + } + public Builder setTextColorConfig(PartnerConfig buttonTextColorConfig) { this.buttonTextColorConfig = buttonTextColorConfig; return this; @@ -200,8 +226,10 @@ public class FooterButtonPartnerConfig { buttonBackgroundConfig, buttonDisableAlphaConfig, buttonDisableBackgroundConfig, + buttonDisableTextColorConfig, buttonIconConfig, buttonTextColorConfig, + buttonMarginStartConfig, buttonTextSizeConfig, buttonMinHeight, buttonTextTypeFaceConfig, diff --git a/main/java/com/google/android/setupcompat/portal/NotificationComponent.java b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java index a90963b..c5865fe 100644 --- a/main/java/com/google/android/setupcompat/portal/NotificationComponent.java +++ b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java @@ -76,6 +76,7 @@ public class NotificationComponent implements Parcelable { @Retention(RetentionPolicy.SOURCE) @IntDef({ + NotificationType.UNKNOWN, NotificationType.INITIAL_ONGOING, NotificationType.PREDEFERRED, NotificationType.PREDEFERRED_PREPARING, diff --git a/main/java/com/google/android/setupcompat/template/FooterActionButton.java b/main/java/com/google/android/setupcompat/template/FooterActionButton.java index 86a06d9..d9726f9 100644 --- a/main/java/com/google/android/setupcompat/template/FooterActionButton.java +++ b/main/java/com/google/android/setupcompat/template/FooterActionButton.java @@ -28,6 +28,7 @@ import androidx.annotation.Nullable; public class FooterActionButton extends Button { @Nullable private FooterButton footerButton; + private boolean isPrimaryButtonStyle = false; public FooterActionButton(Context context, AttributeSet attrs) { super(context, attrs); @@ -54,4 +55,18 @@ public class FooterActionButton extends Button { } return super.onTouchEvent(event); } + + /** + * Sets this footer button is primary button style. + * + * @param isPrimaryButtonStyle True if this button is primary button style. + */ + void setPrimaryButtonStyle(boolean isPrimaryButtonStyle) { + this.isPrimaryButtonStyle = isPrimaryButtonStyle; + } + + /** Returns true when the footer button is primary button style. */ + public boolean isPrimaryButtonStyle() { + return isPrimaryButtonStyle; + } } diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java index b75d972..6d92c40 100644 --- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java +++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java @@ -21,7 +21,7 @@ import static com.google.android.setupcompat.internal.Preconditions.ensureOnMain import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; -import android.content.res.ColorStateList; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Color; import android.os.Build; @@ -29,8 +29,10 @@ import android.os.Build.VERSION_CODES; import android.os.PersistableBundle; import android.util.AttributeSet; import android.view.ContextThemeWrapper; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.ViewStub; import android.widget.Button; import android.widget.LinearLayout; @@ -71,18 +73,18 @@ public class FooterBarMixin implements Mixin { @VisibleForTesting final boolean applyDynamicColor; @VisibleForTesting final boolean useFullDynamicColor; - private LinearLayout buttonContainer; + @VisibleForTesting 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 footerBarPaddingStart; + @VisibleForTesting int footerBarPaddingEnd; @VisibleForTesting int defaultPadding; @ColorInt private final int footerBarPrimaryBackgroundColor; @ColorInt private final int footerBarSecondaryBackgroundColor; @@ -104,11 +106,15 @@ public class FooterBarMixin implements Mixin { if (button != null) { button.setEnabled(enabled); if (applyPartnerResources && !applyDynamicColor) { - updateButtonTextColorWithEnabledState( + + updateButtonTextColorWithStates( button, (id == primaryButtonId || isSecondaryButtonInPrimaryStyle) ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR - : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR); + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, + (id == primaryButtonId || isSecondaryButtonInPrimaryStyle) + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR); } } } @@ -189,6 +195,10 @@ public class FooterBarMixin implements Mixin { footerBarPaddingBottom = a.getDimensionPixelSize( R.styleable.SucFooterBarMixin_sucFooterBarPaddingBottom, defaultPadding); + footerBarPaddingStart = + a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarPaddingStart, 0); + footerBarPaddingEnd = + a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarPaddingEnd, 0); footerBarPrimaryBackgroundColor = a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterBackground, 0); footerBarSecondaryBackgroundColor = @@ -212,11 +222,33 @@ public class FooterBarMixin implements Mixin { metrics.logSecondaryButtonInitialStateVisibility( /* isVisible= */ true, /* isUsingXml= */ true); } + + FooterButtonStyleUtils.clearSavedDefaultTextColor(); + } + + private boolean isFooterButtonAlignedEnd() { + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END)) { + return PartnerConfigHelper.get(context) + .getBoolean(context, PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END, false); + } else { + return false; + } + } + + protected boolean isFooterButtonsEvenlyWeighted() { + if (!isSecondaryButtonInPrimaryStyle) { + return false; + } + // TODO: Support neutral button style in glif layout for phone and tablet + PartnerConfigHelper.get(context); + return context.getResources().getConfiguration().smallestScreenWidthDp >= 600 + && PartnerConfigHelper.isNeutralButtonStyleEnabled(context); } private View addSpace() { LinearLayout buttonContainer = ensureFooterInflated(); - View space = new View(buttonContainer.getContext()); + View space = new View(context); space.setLayoutParams(new LayoutParams(0, 0, 1.0f)); space.setVisibility(View.INVISIBLE); buttonContainer.addView(space); @@ -253,10 +285,13 @@ public class FooterBarMixin implements Mixin { } updateFooterBarPadding( buttonContainer, - buttonContainer.getPaddingLeft(), + footerBarPaddingStart, footerBarPaddingTop, - buttonContainer.getPaddingRight(), + footerBarPaddingEnd, footerBarPaddingBottom); + if (isFooterButtonAlignedEnd()) { + buttonContainer.setGravity(Gravity.END | Gravity.CENTER_VERTICAL); + } } /** @@ -282,19 +317,39 @@ public class FooterBarMixin implements Mixin { 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); + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP)) { + footerBarPaddingTop = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_TOP); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM)) { + footerBarPaddingBottom = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BUTTON_PADDING_BOTTOM); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_PADDING_START)) { + footerBarPaddingStart = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_PADDING_START); + } + if (PartnerConfigHelper.get(context) + .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_PADDING_END)) { + footerBarPaddingEnd = + (int) + PartnerConfigHelper.get(context) + .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_PADDING_END); + } updateFooterBarPadding( buttonContainer, - buttonContainer.getPaddingLeft(), + footerBarPaddingStart, footerBarPaddingTop, - buttonContainer.getPaddingRight(), + footerBarPaddingEnd, footerBarPaddingBottom); if (PartnerConfigHelper.get(context) @@ -339,10 +394,13 @@ public class FooterBarMixin implements Mixin { .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_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) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -352,10 +410,9 @@ public class FooterBarMixin implements Mixin { 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(); + button.setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true); primaryButton = footerButton; primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; - onFooterButtonInflated(button, footerBarPrimaryBackgroundColor); onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig); @@ -410,6 +467,10 @@ public class FooterBarMixin implements Mixin { : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + usePrimaryStyle + ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR + : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR) .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType())) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) @@ -417,6 +478,7 @@ public class FooterBarMixin implements Mixin { usePrimaryStyle ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -426,7 +488,7 @@ public class FooterBarMixin implements Mixin { 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(); + button.setPrimaryButtonStyle(usePrimaryStyle); secondaryButton = footerButton; secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig; @@ -448,6 +510,14 @@ public class FooterBarMixin implements Mixin { Button tempSecondaryButton = getSecondaryButtonView(); buttonContainer.removeAllViews(); + boolean isEvenlyWeightedButtons = isFooterButtonsEvenlyWeighted(); + boolean isLandscape = + context.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + if (isLandscape && isEvenlyWeightedButtons && isFooterButtonAlignedEnd()) { + addSpace(); + } + if (tempSecondaryButton != null) { if (isSecondaryButtonInPrimaryStyle) { // Since the secondary button has the same style (with background) as the primary button, @@ -461,10 +531,55 @@ public class FooterBarMixin implements Mixin { } buttonContainer.addView(tempSecondaryButton); } - addSpace(); + if (!isFooterButtonAlignedEnd() + && (!isEvenlyWeightedButtons || (isEvenlyWeightedButtons && isLandscape))) { + addSpace(); + } if (tempPrimaryButton != null) { buttonContainer.addView(tempPrimaryButton); } + + setEvenlyWeightedButtons(tempPrimaryButton, tempSecondaryButton, isEvenlyWeightedButtons); + } + + private void setEvenlyWeightedButtons( + Button primaryButton, Button secondaryButton, boolean isEvenlyWeighted) { + if (primaryButton != null && secondaryButton != null && isEvenlyWeighted) { + LinearLayout.LayoutParams primaryLayoutParams = + (LinearLayout.LayoutParams) primaryButton.getLayoutParams(); + if (null != primaryLayoutParams) { + primaryLayoutParams.width = 0; + primaryLayoutParams.weight = 1.0f; + primaryButton.setLayoutParams(primaryLayoutParams); + } + + LinearLayout.LayoutParams secondaryLayoutParams = + (LinearLayout.LayoutParams) secondaryButton.getLayoutParams(); + if (null != secondaryLayoutParams) { + secondaryLayoutParams.width = 0; + secondaryLayoutParams.weight = 1.0f; + secondaryButton.setLayoutParams(secondaryLayoutParams); + } + } else { + if (primaryButton != null) { + LinearLayout.LayoutParams primaryLayoutParams = + (LinearLayout.LayoutParams) primaryButton.getLayoutParams(); + if (null != primaryLayoutParams) { + primaryLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; + primaryLayoutParams.weight = 0; + primaryButton.setLayoutParams(primaryLayoutParams); + } + } + if (secondaryButton != null) { + LinearLayout.LayoutParams secondaryLayoutParams = + (LinearLayout.LayoutParams) secondaryButton.getLayoutParams(); + if (null != secondaryLayoutParams) { + secondaryLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; + secondaryLayoutParams.weight = 0; + secondaryButton.setLayoutParams(secondaryLayoutParams); + } + } + } } /** @@ -616,22 +731,23 @@ public class FooterBarMixin implements Mixin { footerButtonPartnerConfig); if (!applyDynamicColor) { // adjust text color based on enabled state - updateButtonTextColorWithEnabledState( - button, footerButtonPartnerConfig.getButtonTextColorConfig()); + updateButtonTextColorWithStates( + button, + footerButtonPartnerConfig.getButtonTextColorConfig(), + footerButtonPartnerConfig.getButtonDisableTextColorConfig()); } } - private void updateButtonTextColorWithEnabledState( - Button button, PartnerConfig buttonTextColorConfig) { + private void updateButtonTextColorWithStates( + Button button, + PartnerConfig buttonTextColorConfig, + PartnerConfig buttonTextDisabledColorConfig) { if (button.isEnabled()) { FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig( context, button, buttonTextColorConfig); } else { - FooterButtonStyleUtils.updateButtonTextDisableColor( - button, - /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle) - ? primaryDefaultTextColor - : secondaryDefaultTextColor); + FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig( + context, button, buttonTextDisabledColorConfig); } } diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java index ef45b5c..ef2aa6b 100644 --- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java +++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java @@ -32,6 +32,7 @@ import android.os.Build; import android.os.Build.VERSION_CODES; import android.util.StateSet; import android.util.TypedValue; +import android.view.ViewGroup; import android.widget.Button; import androidx.annotation.ColorInt; import androidx.annotation.VisibleForTesting; @@ -40,11 +41,14 @@ import com.google.android.setupcompat.internal.FooterButtonPartnerConfig; import com.google.android.setupcompat.internal.Preconditions; import com.google.android.setupcompat.partnerconfig.PartnerConfig; import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import java.util.HashMap; /** Utils for updating the button style. */ public class FooterButtonStyleUtils { private static final float DEFAULT_DISABLED_ALPHA = 0.26f; + private static final HashMap<Integer, ColorStateList> defaultTextColor = new HashMap<>(); + /** Apply the partner primary button style to given {@code button}. */ public static void applyPrimaryButtonPartnerResource( Context context, Button button, boolean applyDynamicColor) { @@ -55,9 +59,12 @@ public class FooterButtonStyleUtils { .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -89,9 +96,12 @@ public class FooterButtonStyleUtils { .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR) .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA) .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR) + .setButtonDisableTextColorConfig( + PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR) .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS) .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA) .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR) + .setMarginStartConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_MARGIN_START) .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE) .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT) .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY) @@ -112,6 +122,9 @@ public class FooterButtonStyleUtils { boolean isButtonIconAtEnd, FooterButtonPartnerConfig footerButtonPartnerConfig) { + // Save defualt text color for the partner config disable button text color not available. + saveButtonDefaultTextColor(button); + // If dynamic color enabled, these colors won't be overrode by partner config. // Instead, these colors align with the current theme colors. if (!applyDynamicColor) { @@ -119,6 +132,9 @@ public class FooterButtonStyleUtils { if (button.isEnabled()) { FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig( context, button, footerButtonPartnerConfig.getButtonTextColorConfig()); + } else { + FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig( + context, button, footerButtonPartnerConfig.getButtonDisableTextColorConfig()); } FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig( context, @@ -133,6 +149,8 @@ public class FooterButtonStyleUtils { applyDynamicColor, footerButtonPartnerConfig.getButtonTextColorConfig(), footerButtonPartnerConfig.getButtonRippleColorAlphaConfig()); + FooterButtonStyleUtils.updateButtonMarginStartWithPartnerConfig( + context, button, footerButtonPartnerConfig.getButtonMarginStartConfig()); FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig( context, button, footerButtonPartnerConfig.getButtonTextSizeConfig()); FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig( @@ -161,10 +179,24 @@ public class FooterButtonStyleUtils { } } - static void updateButtonTextDisableColor(Button button, ColorStateList disabledTextColor) { - // TODO : add disable footer button text color partner config + static void updateButtonTextDisabledColorWithPartnerConfig( + Context context, Button button, PartnerConfig buttonDisableTextColorConfig) { + if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonDisableTextColorConfig)) { + @ColorInt + int color = PartnerConfigHelper.get(context).getColor(context, buttonDisableTextColorConfig); + updateButtonTextDisabledColor(button, color); + } else { + updateButtonTextDisableDefaultColor(button, getButtonDefaultTextCorlor(button)); + } + } + + static void updateButtonTextDisabledColor(Button button, @ColorInt int textColor) { + if (textColor != Color.TRANSPARENT) { + button.setTextColor(ColorStateList.valueOf(textColor)); + } + } - // disable state will use the default disable state color + static void updateButtonTextDisableDefaultColor(Button button, ColorStateList disabledTextColor) { button.setTextColor(disabledTextColor); } @@ -266,16 +298,31 @@ public class FooterButtonStyleUtils { } int[] pressedState = {android.R.attr.state_pressed}; + int[] focusState = {android.R.attr.state_focused}; + int argbColor = convertRgbToArgb(textColor, rippleAlpha); // Set text color for ripple. ColorStateList colorStateList = new ColorStateList( - new int[][] {pressedState, StateSet.NOTHING}, - new int[] {convertRgbToArgb(textColor, rippleAlpha), Color.TRANSPARENT}); + new int[][] {pressedState, focusState, StateSet.NOTHING}, + new int[] {argbColor, argbColor, Color.TRANSPARENT}); rippleDrawable.setColor(colorStateList); } } + static void updateButtonMarginStartWithPartnerConfig( + Context context, Button button, PartnerConfig buttonMarginStartConfig) { + ViewGroup.LayoutParams lp = button.getLayoutParams(); + boolean partnerConfigAvailable = + PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonMarginStartConfig); + if (partnerConfigAvailable && lp instanceof ViewGroup.MarginLayoutParams) { + final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp; + int startMargin = + (int) PartnerConfigHelper.get(context).getDimension(context, buttonMarginStartConfig); + mlp.setMargins(startMargin, mlp.topMargin, mlp.rightMargin, mlp.bottomMargin); + } + } + static void updateButtonTextSizeWithPartnerConfig( Context context, Button button, PartnerConfig buttonTextSizeConfig) { float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig); @@ -367,6 +414,21 @@ public class FooterButtonStyleUtils { button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP); } + private static void saveButtonDefaultTextColor(Button button) { + defaultTextColor.put(button.getId(), button.getTextColors()); + } + + private static ColorStateList getButtonDefaultTextCorlor(Button button) { + if (!defaultTextColor.containsKey(button.getId())) { + throw new IllegalStateException("There is no saved default color for button"); + } + return defaultTextColor.get(button.getId()); + } + + static void clearSavedDefaultTextColor() { + defaultTextColor.clear(); + } + @VisibleForTesting public static GradientDrawable getGradientDrawable(Button button) { // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java index ea54745..5b7c3ad 100644 --- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java +++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java @@ -17,6 +17,7 @@ package com.google.android.setupcompat.util; import android.os.Build; +import androidx.annotation.ChecksSdkIntAtLeast; /** * An util class to check whether the current OS version is higher or equal to sdk version of @@ -25,6 +26,25 @@ import android.os.Build; public final class BuildCompatUtils { /** + * Implementation of BuildCompat.isAtLeastR() suitable for use in Setup + * + * @return Whether the current OS version is higher or equal to R. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) + public static boolean isAtLeastR() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R; + } + /** + * Implementation of BuildCompat.isAtLeastS() suitable for use in Setup + * + * @return Whether the current OS version is higher or equal to S. + */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) + public static boolean isAtLeastS() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + } + + /** * Implementation of BuildCompat.isAtLeast*() suitable for use in Setup * * <p>BuildCompat.isAtLeast*() can be changed by Android Release team, and once that is changed it @@ -40,25 +60,25 @@ public final class BuildCompatUtils { * <p>Supported configurations: * * <ul> - * <li>For current Android release: while new API is not finalized yet (CODENAME = "S", SDK_INT - * = 30|31) - * <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 31) - * <li>For next Android release (CODENAME = "T", SDK_INT = 30+) + * <li>For current Android release: while new API is not finalized yet (CODENAME = "T", SDK_INT + * = 33) + * <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 32) + * <li>For next Android release (CODENAME = "U", SDK_INT = 34+) * </ul> * - * <p>Note that Build.VERSION_CODES.S cannot be used here until final SDK is available in all - * Google3 channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API + * <p>Note that Build.VERSION_CODES.T cannot be used here until final SDK is available in all + * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API * finalization. * - * @return Whether the current OS version is higher or equal to S. + * @return Whether the current OS version is higher or equal to T. */ - public static boolean isAtLeastS() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + public static boolean isAtLeastT() { + if (!isAtLeastS()) { return false; } - return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 31) + return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 33) || (Build.VERSION.CODENAME.length() == 1 - && Build.VERSION.CODENAME.charAt(0) >= 'S' + && Build.VERSION.CODENAME.charAt(0) >= 'T' && Build.VERSION.CODENAME.charAt(0) <= 'Z'); } diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java index 79976bc..84bd68b 100644 --- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java +++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java @@ -65,6 +65,9 @@ public final class WizardManagerHelper { */ public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; + /** Extra for notifying an activity that was called from suggested action activity. */ + public static final String EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW = "isSuwSuggestedActionFlow"; + public static final String EXTRA_THEME = "theme"; public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode"; @@ -122,7 +125,8 @@ public final class WizardManagerHelper { EXTRA_IS_DEFERRED_SETUP, EXTRA_IS_PRE_DEFERRED_SETUP, EXTRA_IS_PORTAL_SETUP, - EXTRA_IS_SETUP_FLOW)) { + EXTRA_IS_SETUP_FLOW, + EXTRA_IS_SUW_SUGGESTED_ACTION_FLOW)) { dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false)); } diff --git a/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java b/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java index da1ab34..1157fae 100644 --- a/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java +++ b/main/java/com/google/android/setupcompat/view/ButtonBarLayout.java @@ -18,9 +18,12 @@ package com.google.android.setupcompat.view; import android.content.Context; import android.util.AttributeSet; +import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; import com.google.android.setupcompat.R; +import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper; +import com.google.android.setupcompat.template.FooterActionButton; /** * An extension of LinearLayout that automatically switches to vertical orientation when it can't @@ -62,7 +65,7 @@ public class ButtonBarLayout extends LinearLayout { super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec); - if (getMeasuredWidth() > widthSize) { + if (!isFooterButtonsEventlyWeighted(getContext()) && (getMeasuredWidth() > widthSize)) { setStacked(true); // Measure again in the new orientation. @@ -86,6 +89,7 @@ public class ButtonBarLayout extends LinearLayout { if (stacked) { child.setTag(R.id.suc_customization_original_weight, childParams.weight); childParams.weight = 0; + childParams.leftMargin = 0; } else { Float weight = (Float) child.getTag(R.id.suc_customization_original_weight); if (weight != null) { @@ -103,6 +107,8 @@ public class ButtonBarLayout extends LinearLayout { } if (stacked) { + // When stacked, the buttons need to be kept in the center of the button bar. + setHorizontalGravity(Gravity.CENTER); // HACK: In the default button bar style, the left and right paddings are not // balanced to compensate for different alignment for borderless (left) button and // the raised (right) button. When it's stacked, we want the buttons to be centered, @@ -115,4 +121,28 @@ public class ButtonBarLayout extends LinearLayout { setPadding(originalPaddingLeft, getPaddingTop(), originalPaddingRight, getPaddingBottom()); } } + + private boolean isFooterButtonsEventlyWeighted(Context context) { + int childCount = getChildCount(); + int primayButtonCount = 0; + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (child instanceof FooterActionButton) { + if (((FooterActionButton) child).isPrimaryButtonStyle()) { + primayButtonCount += 1; + } + } + } + if (primayButtonCount != 2) { + return false; + } + + // TODO: Support neutral button style in glif layout for phone and tablet + if (context.getResources().getConfiguration().smallestScreenWidthDp >= 600 + && PartnerConfigHelper.shouldApplyExtendedPartnerConfig(context)) { + return true; + } else { + return false; + } + } } diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java index 280ab81..442e86c 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java @@ -40,6 +40,14 @@ public enum PartnerConfig { // The min height of the footer buttons CONFIG_FOOTER_BAR_MIN_HEIGHT(PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT, ResourceType.DIMENSION), + // The padding start of the footer bar + CONFIG_FOOTER_BAR_PADDING_START( + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_START, ResourceType.DIMENSION), + + // The padding end of the footer bar + CONFIG_FOOTER_BAR_PADDING_END( + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_END, ResourceType.DIMENSION), + // The same as "windowLightNavigationBar". If set true, the navigation bar icons will be drawn // such that it is compatible with a light navigation bar background. CONFIG_LIGHT_NAVIGATION_BAR(PartnerConfigKey.KEY_LIGHT_NAVIGATION_BAR, ResourceType.BOOL), @@ -108,6 +116,10 @@ public enum PartnerConfig { CONFIG_FOOTER_BUTTON_MIN_HEIGHT( PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT, ResourceType.DIMENSION), + // Make the footer buttons all aligned the end + CONFIG_FOOTER_BUTTON_ALIGNED_END( + PartnerConfigKey.KEY_FOOTER_BUTTON_ALIGNED_END, ResourceType.BOOL), + // Disabled background alpha of the footer buttons CONFIG_FOOTER_BUTTON_DISABLED_ALPHA( PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, ResourceType.FRACTION), @@ -116,6 +128,14 @@ public enum PartnerConfig { CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR( PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR, ResourceType.COLOR), + // Disabled text color of the primary footer button + CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR( + PartnerConfigKey.KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR, ResourceType.COLOR), + + // Disabled text color of the secondary footer button + CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR( + PartnerConfigKey.KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR, ResourceType.COLOR), + // Background color of the primary footer button CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR( PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, ResourceType.COLOR), @@ -124,6 +144,10 @@ public enum PartnerConfig { CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR( PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, ResourceType.COLOR), + // Margin start of the primary footer button + CONFIG_FOOTER_PRIMARY_BUTTON_MARGIN_START( + PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START, ResourceType.DIMENSION), + // Background color of the secondary footer button CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR( PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR, ResourceType.COLOR), @@ -132,6 +156,10 @@ public enum PartnerConfig { CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR( PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, ResourceType.COLOR), + // Margin start of the secondary footer button + CONFIG_FOOTER_SECONDARY_BUTTON_MARGIN_START( + PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START, ResourceType.DIMENSION), + // Background color of layout CONFIG_LAYOUT_BACKGROUND_COLOR(PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, ResourceType.COLOR), @@ -141,6 +169,10 @@ public enum PartnerConfig { // Margin end of the layout CONFIG_LAYOUT_MARGIN_END(PartnerConfigKey.KEY_LAYOUT_MARGIN_END, ResourceType.DIMENSION), + // Middle horizontal spacing of the landscape layout + CONFIG_LAND_MIDDLE_HORIZONTAL_SPACING( + PartnerConfigKey.KEY_LAND_MIDDLE_HORIZONTAL_SPACING, ResourceType.DIMENSION), + // Text color of the header CONFIG_HEADER_TEXT_COLOR(PartnerConfigKey.KEY_HEADER_TEXT_COLOR, ResourceType.COLOR), @@ -207,6 +239,10 @@ public enum PartnerConfig { // Font family of the description CONFIG_DESCRIPTION_FONT_FAMILY(PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, ResourceType.STRING), + // Font family of the link text + CONFIG_DESCRIPTION_LINK_FONT_FAMILY( + PartnerConfigKey.KEY_DESCRIPTION_LINK_FONT_FAMILY, ResourceType.STRING), + // Margin top of the description text CONFIG_DESCRIPTION_TEXT_MARGIN_TOP( PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, ResourceType.DIMENSION), @@ -291,11 +327,11 @@ public enum PartnerConfig { // The divider of list items are showing on the pages. CONFIG_ITEMS_DIVIDER_SHOWN(PartnerConfigKey.KEY_ITEMS_DIVIDER_SHOWN, ResourceType.BOOL), - // The intrinsic width of the card view for foldabe/tablet. + // The intrinsic width of the card view for foldable/tablet. CONFIG_CARD_VIEW_INTRINSIC_WIDTH( PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH, ResourceType.DIMENSION), - // The intrinsic height of the card view for foldabe/tablet. + // The intrinsic height of the card view for foldable/tablet. CONFIG_CARD_VIEW_INTRINSIC_HEIGHT( PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT, ResourceType.DIMENSION), @@ -414,7 +450,19 @@ public enum PartnerConfig { // The height of the header of the loading layout. CONFIG_LOADING_LAYOUT_HEADER_HEIGHT( - PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION); + PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION), + + // Use the fullscreen style lottie animation. + CONFIG_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED( + PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, ResourceType.BOOL), + + // The margin top of progress bar. + CONFIG_PROGRESS_BAR_MARGIN_TOP( + PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, ResourceType.DIMENSION), + + // The margin bottom of progress bar. + CONFIG_PROGRESS_BAR_MARGIN_BOTTOM( + PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM, ResourceType.DIMENSION); /** Resource type of the partner resources type. */ public enum ResourceType { diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java index 2ca8876..3525fa1 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java @@ -62,12 +62,17 @@ public class PartnerConfigHelper { @VisibleForTesting public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled"; + @VisibleForTesting + public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled"; + @VisibleForTesting static Bundle suwDayNightEnabledBundle = null; @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null; @VisibleForTesting public static Bundle applyDynamicColorBundle = null; + @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null; + private static PartnerConfigHelper instance = null; @VisibleForTesting Bundle resultBundle = null; @@ -81,6 +86,14 @@ public class PartnerConfigHelper { private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT; + /** + * When testing related to fake PartnerConfigHelper instance, should sync the following saved + * config with testing environment. + */ + @VisibleForTesting public static int savedScreenHeight = Configuration.SCREEN_HEIGHT_DP_UNDEFINED; + + @VisibleForTesting public static int savedScreenWidth = Configuration.SCREEN_WIDTH_DP_UNDEFINED; + public static synchronized PartnerConfigHelper get(@NonNull Context context) { if (!isValidInstance(context)) { instance = new PartnerConfigHelper(context); @@ -93,15 +106,21 @@ public class PartnerConfigHelper { if (instance == null) { savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; savedOrientation = currentConfig.orientation; + savedScreenWidth = currentConfig.screenWidthDp; + savedScreenHeight = currentConfig.screenHeightDp; return false; } else { - if (isSetupWizardDayNightEnabled(context) - && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode) { + boolean uiModeChanged = + isSetupWizardDayNightEnabled(context) + && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode; + if (uiModeChanged + || currentConfig.orientation != savedOrientation + || currentConfig.screenWidthDp != savedScreenWidth + || currentConfig.screenHeightDp != savedScreenHeight) { savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK; - resetInstance(); - return false; - } else if (currentConfig.orientation != savedOrientation) { savedOrientation = currentConfig.orientation; + savedScreenHeight = currentConfig.screenHeightDp; + savedScreenWidth = currentConfig.screenWidthDp; resetInstance(); return false; } @@ -315,7 +334,7 @@ public class PartnerConfigHelper { result = resource.getBoolean(resId); partnerResourceCache.put(resourceConfig, result); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -364,7 +383,7 @@ public class PartnerConfigHelper { result = getDimensionFromTypedValue( context, (TypedValue) partnerResourceCache.get(resourceConfig)); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -408,7 +427,7 @@ public class PartnerConfigHelper { result = resource.getFraction(resId, 1, 1); partnerResourceCache.put(resourceConfig, result); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -441,7 +460,7 @@ public class PartnerConfigHelper { result = resource.getInteger(resId); partnerResourceCache.put(resourceConfig, result); - } catch (NullPointerException exception) { + } catch (NullPointerException | NotFoundException exception) { // fall through } return result; @@ -503,6 +522,8 @@ public class PartnerConfigHelper { /* arg= */ null, /* extras= */ null); partnerResourceCache.clear(); + Log.i( + TAG, "PartnerConfigsBundle=" + (resultBundle != null ? resultBundle.size() : "(null)")); } catch (IllegalArgumentException | SecurityException exception) { Log.w(TAG, "Fail to get config from suw provider"); } @@ -550,6 +571,7 @@ public class PartnerConfigHelper { suwDayNightEnabledBundle = null; applyExtendedPartnerConfigBundle = null; applyDynamicColorBundle = null; + applyNeutralButtonStyleBundle = null; } /** @@ -629,6 +651,29 @@ public class PartnerConfigHelper { && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false)); } + /** Returns true if the SetupWizard supports the neutral button style during setup flow. */ + public static boolean isNeutralButtonStyleEnabled(@NonNull Context context) { + if (applyNeutralButtonStyleBundle == null) { + try { + applyNeutralButtonStyleBundle = + context + .getContentResolver() + .call( + getContentUri(), + IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, + /* arg= */ null, + /* extras= */ null); + } catch (IllegalArgumentException | SecurityException exception) { + Log.w(TAG, "Neutral button style supporting status unknown; return as false."); + applyNeutralButtonStyleBundle = null; + return false; + } + } + + return (applyNeutralButtonStyleBundle != null + && applyNeutralButtonStyleBundle.getBoolean(IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD, false)); + } + @VisibleForTesting static Uri getContentUri() { return new Uri.Builder() diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java index c7444a5..cbf72f5 100644 --- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java +++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java @@ -31,6 +31,8 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_NAVIGATION_BAR_DIVIDER_COLOR, PartnerConfigKey.KEY_FOOTER_BAR_BG_COLOR, PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT, + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_START, + PartnerConfigKey.KEY_FOOTER_BAR_PADDING_END, PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY, PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER, PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL, @@ -47,15 +49,21 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE, PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_STYLE, PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT, + PartnerConfigKey.KEY_FOOTER_BUTTON_ALIGNED_END, PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR, PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR, + PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START, + PartnerConfigKey.KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR, PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR, PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR, + PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START, + PartnerConfigKey.KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR, PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, PartnerConfigKey.KEY_LAYOUT_MARGIN_START, PartnerConfigKey.KEY_LAYOUT_MARGIN_END, + PartnerConfigKey.KEY_LAND_MIDDLE_HORIZONTAL_SPACING, PartnerConfigKey.KEY_HEADER_TEXT_SIZE, PartnerConfigKey.KEY_HEADER_TEXT_COLOR, PartnerConfigKey.KEY_HEADER_FONT_FAMILY, @@ -75,6 +83,7 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_DESCRIPTION_TEXT_COLOR, PartnerConfigKey.KEY_DESCRIPTION_LINK_TEXT_COLOR, PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, + PartnerConfigKey.KEY_DESCRIPTION_LINK_FONT_FAMILY, PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM, PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, @@ -128,6 +137,9 @@ import java.lang.annotation.RetentionPolicy; PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END, PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM, PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, + PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, + PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, + PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM, }) // TODO: can be removed and always reference PartnerConfig.getResourceName()? @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) @@ -155,6 +167,12 @@ public @interface PartnerConfigKey { // The min height of the footer bar String KEY_FOOTER_BAR_MIN_HEIGHT = "setup_compat_footer_bar_min_height"; + // The padding start of the footer bar + String KEY_FOOTER_BAR_PADDING_START = "setup_compat_footer_bar_padding_start"; + + // The padding end of the footer bar + String KEY_FOOTER_BAR_PADDING_END = "setup_compat_footer_bar_padding_end"; + // The font face used in footer buttons. This must be a string reference to a font that is // available in the system. Font references (@font or @xml) are not allowed. String KEY_FOOTER_BUTTON_FONT_FAMILY = "setup_compat_footer_button_font_family"; @@ -204,6 +222,9 @@ public @interface PartnerConfigKey { // The min height of the footer buttons String KEY_FOOTER_BUTTON_MIN_HEIGHT = "setup_compat_footer_button_min_height"; + // Make the footer buttons all aligned the end + String KEY_FOOTER_BUTTON_ALIGNED_END = "setup_compat_footer_button_aligned_end"; + // Disabled background alpha of the footer buttons String KEY_FOOTER_BUTTON_DISABLED_ALPHA = "setup_compat_footer_button_disabled_alpha"; @@ -216,12 +237,26 @@ public @interface PartnerConfigKey { // Text color of the primary footer button String KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR = "setup_compat_footer_primary_button_text_color"; + // Margin start of the primary footer button + String KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START = "setup_compat_footer_primary_button_margin_start"; + + // Disabled text color of the primary footer button + String KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR = "setup_compat_primary_button_disabled_text_color"; + // Background color of the secondary footer button String KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR = "setup_compat_footer_secondary_button_bg_color"; // Text color of the secondary footer button String KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR = "setup_compat_footer_secondary_button_text_color"; + // Margin start of the secondary footer button + String KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START = + "setup_compat_footer_secondary_button_margin_start"; + + // Disabled text color of the secondary footer button + String KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR = + "setup_compat_secondary_button_disabled_text_color"; + // Background color of layout String KEY_LAYOUT_BACKGROUND_COLOR = "setup_design_layout_bg_color"; @@ -231,6 +266,9 @@ public @interface PartnerConfigKey { // Margin end of the layout String KEY_LAYOUT_MARGIN_END = "setup_design_layout_margin_end"; + // Middle horizontal spacing of the landscape layout + String KEY_LAND_MIDDLE_HORIZONTAL_SPACING = "setup_design_land_middle_horizontal_spacing"; + // Text size of the header String KEY_HEADER_TEXT_SIZE = "setup_design_header_text_size"; @@ -290,6 +328,9 @@ public @interface PartnerConfigKey { // Font family of the description String KEY_DESCRIPTION_FONT_FAMILY = "setup_design_description_font_family"; + // Font family of the link text + String KEY_DESCRIPTION_LINK_FONT_FAMILY = "setup_design_description_link_font_family"; + // Margin top of the header text String KEY_DESCRIPTION_TEXT_MARGIN_TOP = "setup_design_description_text_margin_top"; @@ -474,4 +515,14 @@ public @interface PartnerConfigKey { // A height of the header of loading layout. String KEY_LOADING_LAYOUT_HEADER_HEIGHT = "loading_layout_header_height"; + + // Use the fullscreen style lottie animation. + String KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED = + "loading_layout_full_screen_illustration_enabled"; + + // A margin top of the content frame of progress bar. + String KEY_PROGRESS_BAR_MARGIN_TOP = "setup_design_progress_bar_margin_top"; + + // A margin bottom of the content frame of progress bar. + String KEY_PROGRESS_BAR_MARGIN_BOTTOM = "setup_design_progress_bar_margin_bottom"; } diff --git a/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl b/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl new file mode 100644 index 0000000..2b83576 --- /dev/null +++ b/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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.portal; + +import com.google.android.setupcompat.portal.IPortalProgressCallback; +import com.google.android.setupcompat.portal.TaskComponent; + +/** + * Declares the interface for portal used by GmsCore. + */ +interface ISetupNotificationServicePortalExtension { + IPortalProgressCallback registerTask(in TaskComponent component) = 1; + + boolean removeTask(String packageName, String taskName) = 2; +}
\ No newline at end of file diff --git a/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl b/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl new file mode 100644 index 0000000..8a4dfb3 --- /dev/null +++ b/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 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.portal; + +/** + * A class that represents how a persistent notification is to be presented to the user using the + * {@link com.google.android.setupcompat.portal.ISetupNotificationServicePortalExtension }. + */ +parcelable TaskComponent;
\ No newline at end of file diff --git a/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java b/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java new file mode 100644 index 0000000..4f1b884 --- /dev/null +++ b/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 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.portal; + +/** Constant values used for PortalExtension */ +public class PortalExtensionConstants { + public static final String BIND_SERVICE_INTENT_ACTION = + "com.google.android.setupcompat.portal.SetupNotificationService.BIND_EXTENSION"; + + private PortalExtensionConstants() {} + ; +} diff --git a/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java b/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java new file mode 100644 index 0000000..16d35a3 --- /dev/null +++ b/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2020 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.portal; + +import android.content.Intent; +import android.os.Parcel; +import android.os.Parcelable; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import com.google.android.setupcompat.internal.Preconditions; + +/** + * A class that represents how a persistent notification is to be presented to the user using the + * {@link com.google.android.setupcompat.portal.ISetupNotificationServicePortalExtension }. + */ +public class TaskComponent implements Parcelable { + private final String packageName; + private final String taskName; + @StringRes private final int displayNameResId; + @DrawableRes private final int displayIconResId; + private final Intent itemClickIntent; + + private TaskComponent( + String packageName, + String taskName, + @StringRes int displayNameResId, + @DrawableRes int displayIconResId, + Intent itemClickIntent) { + this.packageName = packageName; + this.taskName = taskName; + this.displayNameResId = displayNameResId; + this.displayIconResId = displayIconResId; + this.itemClickIntent = itemClickIntent; + } + + /** Returns a new instance of {@link Builder}. */ + public static Builder newBuilder() { + return new Builder(); + } + + /** Returns the package name where the service exist. */ + @NonNull + public String getPackageName() { + return packageName; + } + + /** Returns the service class name */ + @NonNull + public String getTaskName() { + return taskName; + } + + /** Returns the string resource id of display name. */ + @StringRes + public int getDisplayName() { + return displayNameResId; + } + + /** Returns the drawable resource id of display icon. */ + @DrawableRes + public int getDisplayIcon() { + return displayIconResId; + } + + /** Returns the Intent to start the user interface while progress item click. */ + public Intent getItemClickIntent() { + return itemClickIntent; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(getPackageName()); + dest.writeString(getTaskName()); + dest.writeInt(getDisplayName()); + dest.writeInt(getDisplayIcon()); + dest.writeParcelable(getItemClickIntent(), 0); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator<TaskComponent> CREATOR = + new Creator<TaskComponent>() { + @Override + public TaskComponent createFromParcel(Parcel in) { + return TaskComponent.newBuilder() + .setPackageName(in.readString()) + .setTaskName(in.readString()) + .setDisplayName(in.readInt()) + .setDisplayIcon(in.readInt()) + .setItemClickIntent(in.readParcelable(Intent.class.getClassLoader())) + .build(); + } + + @Override + public TaskComponent[] newArray(int size) { + return new TaskComponent[size]; + } + }; + + /** Builder class for {@link com.google.android.setupcompat.portal.TaskComponent} objects. */ + public static class Builder { + private String packageName; + private String taskName; + @StringRes private int displayNameResId; + @DrawableRes private int displayIconResId; + private Intent itemClickIntent; + + /** Sets the packages name which is the service exists */ + public Builder setPackageName(@NonNull String packageName) { + this.packageName = packageName; + return this; + } + + /** Sets a name to identify what task this progress is. */ + public Builder setTaskName(@NonNull String taskName) { + this.taskName = taskName; + return this; + } + + /** Sets the name which is displayed on PortalActivity */ + public Builder setDisplayName(@StringRes int displayNameResId) { + this.displayNameResId = displayNameResId; + return this; + } + + /** Sets the icon which is display on PortalActivity */ + public Builder setDisplayIcon(@DrawableRes int displayIconResId) { + this.displayIconResId = displayIconResId; + return this; + } + + public Builder setItemClickIntent(Intent itemClickIntent) { + this.itemClickIntent = itemClickIntent; + return this; + } + + public TaskComponent build() { + Preconditions.checkNotNull(packageName, "packageName cannot be null."); + Preconditions.checkNotNull(taskName, "serviceClass cannot be null."); + Preconditions.checkNotNull(itemClickIntent, "Item click intent cannot be null"); + Preconditions.checkArgument(displayNameResId != 0, "Invalidate resource id of display name"); + Preconditions.checkArgument(displayIconResId != 0, "Invalidate resource id of display icon"); + + return new TaskComponent( + packageName, taskName, displayNameResId, displayIconResId, itemClickIntent); + } + } +} |