Merge sc-v2-dev-plus-aosp-without-vendor@8084891
Bug: 214455710
Merged-In: Ife565ede7fe4b86c8c854fc62d4b042b777dd226
Change-Id: I41b0f264d344daeb60050231160084c448083f16
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 @@
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 @@
PartnerConfig buttonBackgroundConfig,
PartnerConfig buttonDisableAlphaConfig,
PartnerConfig buttonDisableBackgroundConfig,
+ PartnerConfig buttonDisableTextColorConfig,
PartnerConfig buttonIconConfig,
PartnerConfig buttonTextColorConfig,
+ PartnerConfig buttonMarginStartConfig,
PartnerConfig buttonTextSizeConfig,
PartnerConfig buttonMinHeightConfig,
PartnerConfig buttonTextTypeFaceConfig,
@@ -50,6 +54,7 @@
this.partnerTheme = partnerTheme;
this.buttonTextColorConfig = buttonTextColorConfig;
+ this.buttonMarginStartConfig = buttonMarginStartConfig;
this.buttonTextSizeConfig = buttonTextSizeConfig;
this.buttonMinHeightConfig = buttonMinHeightConfig;
this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig;
@@ -57,6 +62,7 @@
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 @@
return buttonDisableBackgroundConfig;
}
+ public PartnerConfig getButtonDisableTextColorConfig() {
+ return buttonDisableTextColorConfig;
+ }
+
public PartnerConfig getButtonIconConfig() {
return buttonIconConfig;
}
@@ -86,6 +96,10 @@
return buttonTextColorConfig;
}
+ public PartnerConfig getButtonMarginStartConfig() {
+ return buttonMarginStartConfig;
+ }
+
public PartnerConfig getButtonMinHeightConfig() {
return buttonMinHeightConfig;
}
@@ -116,8 +130,10 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
}
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 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.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 @@
@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 @@
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 @@
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 @@
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 @@
}
updateFooterBarPadding(
buttonContainer,
- buttonContainer.getPaddingLeft(),
+ footerBarPaddingStart,
footerBarPaddingTop,
- buttonContainer.getPaddingRight(),
+ footerBarPaddingEnd,
footerBarPaddingBottom);
+ if (isFooterButtonAlignedEnd()) {
+ buttonContainer.setGravity(Gravity.END | Gravity.CENTER_VERTICAL);
+ }
}
/**
@@ -282,19 +317,39 @@
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 @@
.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 @@
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 @@
: 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 @@
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 @@
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 @@
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 @@
}
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 @@
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.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.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 @@
.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 @@
.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 @@
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 @@
if (button.isEnabled()) {
FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
context, button, footerButtonPartnerConfig.getButtonTextColorConfig());
+ } else {
+ FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonDisableTextColorConfig());
}
FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
context,
@@ -133,6 +149,8 @@
applyDynamicColor,
footerButtonPartnerConfig.getButtonTextColorConfig(),
footerButtonPartnerConfig.getButtonRippleColorAlphaConfig());
+ FooterButtonStyleUtils.updateButtonMarginStartWithPartnerConfig(
+ context, button, footerButtonPartnerConfig.getButtonMarginStartConfig());
FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig(
context, button, footerButtonPartnerConfig.getButtonTextSizeConfig());
FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig(
@@ -161,10 +179,24 @@
}
}
- 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));
+ }
+ }
- // disable state will use the default disable state color
+ static void updateButtonTextDisabledColor(Button button, @ColorInt int textColor) {
+ if (textColor != Color.TRANSPARENT) {
+ button.setTextColor(ColorStateList.valueOf(textColor));
+ }
+ }
+
+ static void updateButtonTextDisableDefaultColor(Button button, ColorStateList disabledTextColor) {
button.setTextColor(disabledTextColor);
}
@@ -266,16 +298,31 @@
}
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 @@
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 @@
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 @@
* <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 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 @@
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 @@
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 @@
super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
- if (getMeasuredWidth() > widthSize) {
+ if (!isFooterButtonsEventlyWeighted(getContext()) && (getMeasuredWidth() > widthSize)) {
setStacked(true);
// Measure again in the new orientation.
@@ -86,6 +89,7 @@
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 @@
}
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
@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 @@
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 @@
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 @@
result = resource.getBoolean(resId);
partnerResourceCache.put(resourceConfig, result);
- } catch (NullPointerException exception) {
+ } catch (NullPointerException | NotFoundException exception) {
// fall through
}
return result;
@@ -364,7 +383,7 @@
result =
getDimensionFromTypedValue(
context, (TypedValue) partnerResourceCache.get(resourceConfig));
- } catch (NullPointerException exception) {
+ } catch (NullPointerException | NotFoundException exception) {
// fall through
}
return result;
@@ -408,7 +427,7 @@
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 @@
result = resource.getInteger(resId);
partnerResourceCache.put(resourceConfig, result);
- } catch (NullPointerException exception) {
+ } catch (NullPointerException | NotFoundException exception) {
// fall through
}
return result;
@@ -503,6 +522,8 @@
/* 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 @@
suwDayNightEnabledBundle = null;
applyExtendedPartnerConfigBundle = null;
applyDynamicColorBundle = null;
+ applyNeutralButtonStyleBundle = null;
}
/**
@@ -629,6 +651,29 @@
&& 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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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 @@
// 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);
+ }
+ }
+}