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);
+    }
+  }
+}