Merge sc-dev-plus-aosp-without-vendor@7634622

Merged-In: Ia1aa63b7495a82d72da0ca5a70e1bda473cedc91
Change-Id: I40ce7b1692e93617aec9de7a0d3a8b154fdf7a02
diff --git a/Android.bp b/Android.bp
index 353706d..01e654d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -19,6 +19,52 @@
     ],
 }
 
+filegroup {
+    name: "Aidls",
+    srcs: [
+        "main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl",
+    ],
+    path: "main/aidl",
+}
+
+filegroup {
+    name: "AidlsPortal",
+    srcs: [
+        "main/aidl/com/google/android/setupcompat/portal/*.aidl",
+    ],
+    path: "main/aidl",
+}
+
+filegroup {
+    name: "Srcs",
+    srcs: [
+        "main/java/com/google/android/setupcompat/*.java",
+        "main/java/com/google/android/setupcompat/internal/*.java",
+        "main/java/com/google/android/setupcompat/logging/*.java",
+        "main/java/com/google/android/setupcompat/logging/internal/*.java",
+        "main/java/com/google/android/setupcompat/template/*.java",
+        "main/java/com/google/android/setupcompat/util/*.java",
+        "main/java/com/google/android/setupcompat/view/*.java",
+    ],
+    path: "main/java",
+}
+
+filegroup {
+    name: "SrcsPartnerConfig",
+    srcs: [
+        "partnerconfig/java/**/*.java",
+    ],
+    path: "partnerconfig/java",
+}
+
+filegroup {
+    name: "SrcsPortal",
+    srcs: [
+        "main/java/com/google/android/setupcompat/portal/*.java",
+    ],
+    path: "main/java",
+}
+
 android_library {
     name: "setupcompat",
     manifest: "AndroidManifest.xml",
@@ -26,9 +72,11 @@
         "main/res",
     ],
     srcs: [
-        "main/java/**/*.java",
-        "partnerconfig/java/**/*.java",
-        "main/aidl/**/*.aidl",
+        ":Aidls",
+        ":AidlsPortal",
+        ":Srcs",
+        ":SrcsPartnerConfig",
+        ":SrcsPortal",
     ],
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/exempting_lint_checks.txt b/exempting_lint_checks.txt
new file mode 100644
index 0000000..90b8caf
--- /dev/null
+++ b/exempting_lint_checks.txt
@@ -0,0 +1,7 @@
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java: CustomViewStyleable: attrs, R.styleable.SucPartnerCustomizationLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/internal/TemplateLayout.java: CustomViewStyleable: getContext().obtainStyledAttributes(attrs, R.styleable.SucTemplateLayout, defStyleAttr, 0);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/util/SystemBarHelper.java: AnnotateVersionCheck: public static void setImeInsetView(final View view) {
diff --git a/grandfathered_lint_checks.txt b/grandfathered_lint_checks.txt
deleted file mode 100644
index e69de29..0000000
--- a/grandfathered_lint_checks.txt
+++ /dev/null
diff --git a/lint-baseline.xml b/lint-baseline.xml
index ac87760..43e81f8 100644
--- a/lint-baseline.xml
+++ b/lint-baseline.xml
@@ -30,7 +30,7 @@
         errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="external/setupcompat/main/res/values/styles.xml"
-            line="52"
+            line="54"
             column="15"/>
     </issue>
 
@@ -41,7 +41,7 @@
         errorLine2="                                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="external/setupcompat/main/res/values/styles.xml"
-            line="58"
+            line="60"
             column="59"/>
     </issue>
 
@@ -52,7 +52,7 @@
         errorLine2="              ~~~~~~~~~~~~~~~~~~~~~~~~~">
         <location
             file="external/setupcompat/main/res/values/styles.xml"
-            line="66"
+            line="68"
             column="15"/>
     </issue>
 
diff --git a/main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl
new file mode 100644
index 0000000..13303d8
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressCallback.aidl
@@ -0,0 +1,72 @@
+/*
+ * 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.os.Bundle;
+
+/**
+ * Interface for progress service to update progress to SUW. Clients should
+ * update progress at least once a minute, or set a pending reason to stop
+ * update progress. Without progress update and pending reason. We considering
+ * the progress service is no response will suspend it and unbinde service.
+ */
+interface IPortalProgressCallback {
+  /**
+   * Sets completed amount.
+   */
+  Bundle setProgressCount(int completed, int failed, int total) = 0;
+
+  /**
+   * Sets completed percentage.
+   */
+  Bundle setProgressPercentage(int percentage) = 1;
+
+  /**
+   * Sets the summary shows on portal activity.
+   */
+  Bundle setSummary(String summary) = 2;
+
+  /**
+   * Let SUW knows the progress is pending now, and stop update progress.
+   * @param reasonResId The resource identifier.
+   * @param quantity The number used to get the correct string for the current language's
+   *           plural rules
+   * @param formatArgs The format arguments that will be used for substitution.
+   */
+  Bundle setPendingReason(int reasonResId, int quantity, in int[] formatArgs, int reason) = 3;
+
+  /**
+   * Once the progress completed, and service can be destroy. Call this function.
+   * SUW will unbind the service.
+   * @param resId The resource identifier.
+   * @param quantity The number used to get the correct string for the current language's
+   *           plural rules
+   * @param formatArgs The format arguments that will be used for substitution.
+   */
+  Bundle setComplete(int resId, int quantity, in int[] formatArgs) = 4;
+
+  /**
+   * Once the progress failed, and not able to finish the progress. Should call
+   * this function. SUW will unbind service after receive setFailure. Client can
+   * registerProgressService again once the service is ready to execute.
+   * @param resId The resource identifier.
+   * @param quantity The number used to get the correct string for the current language's
+   *           plural rules
+   * @param formatArgs The format arguments that will be used for substitution.
+   */
+  Bundle setFailure(int resId, int quantity, in int[] formatArgs) = 5;
+}
\ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl
new file mode 100644
index 0000000..d741125
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/IPortalProgressService.aidl
@@ -0,0 +1,55 @@
+/*
+ * 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.os.Bundle;
+import com.google.android.setupcompat.portal.IPortalProgressCallback;
+
+/**
+ * Interface of progress service, all servics needs to run during onboarding, and would like
+ * to consolidate notifications with SetupNotificationService, should implement this interface.
+ * So that SetupNotificationService can bind the progress service and run below
+ * SetupNotificationService.
+ */
+interface IPortalProgressService {
+  /**
+  * Called when the Portal notification is started.
+  */
+  oneway void onPortalSessionStart() = 0;
+
+  /**
+   * Provides a non-null callback after service connected.
+   */
+  oneway void onSetCallback(IPortalProgressCallback callback) = 1;
+
+  /**
+   * Called when progress timed out.
+   */
+  oneway void onSuspend() = 2;
+
+  /**
+   * User clicks "User mobile data" on portal activity.
+   * All running progress should agree to use mobile data.
+   */
+  oneway void onAllowMobileData(boolean allowed) = 3;
+
+  /**
+   * Portal service calls to get remaining downloading size(MB) from progress service.
+   */
+  @nullable
+  Bundle onGetRemainingValues() = 4;
+}
\ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl b/main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl
new file mode 100644
index 0000000..56d39e5
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/IPortalRegisterResultListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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;
+
+/** Listener to listen the result of registerProgressService */
+interface IPortalRegisterResultListener {
+  void onResult(in Bundle result) = 0;
+}
\ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl b/main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl
new file mode 100644
index 0000000..9f9b1d8
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/ISetupNotificationService.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.os.UserHandle;
+import com.google.android.setupcompat.portal.IPortalRegisterResultListener;
+import com.google.android.setupcompat.portal.NotificationComponent;
+import com.google.android.setupcompat.portal.ProgressServiceComponent;
+
+/**
+ * Declares the interface for notification related service methods.
+ */
+interface ISetupNotificationService {
+  /** Register a notification without progress service */
+  boolean registerNotification(in NotificationComponent component) = 0;
+  void unregisterNotification(in NotificationComponent component) = 1;
+
+  /** Register a progress service */
+  void registerProgressService(in ProgressServiceComponent component, in UserHandle userHandle, IPortalRegisterResultListener listener) = 2;
+
+  /** Checks the progress connection still alive or not.  */
+  boolean isProgressServiceAlive(in ProgressServiceComponent component, in UserHandle userHandle) = 3;
+
+  /** Checks portal avaailable or not. */
+  boolean isPortalAvailable() = 4;
+}
\ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl b/main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl
new file mode 100644
index 0000000..5de3f76
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/NotificationComponent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable NotificationComponent;
\ No newline at end of file
diff --git a/main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl b/main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl
new file mode 100644
index 0000000..6a6e120
--- /dev/null
+++ b/main/aidl/com/google/android/setupcompat/portal/ProgressServiceComponent.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+ parcelable ProgressServiceComponent;
\ No newline at end of file
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index fac4b39..e5ba0c5 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -26,7 +26,6 @@
 import android.os.Build.VERSION_CODES;
 import android.os.PersistableBundle;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -42,12 +41,14 @@
 import com.google.android.setupcompat.template.FooterButton;
 import com.google.android.setupcompat.template.StatusBarMixin;
 import com.google.android.setupcompat.template.SystemNavBarMixin;
+import com.google.android.setupcompat.util.BuildCompatUtils;
+import com.google.android.setupcompat.util.Logger;
 import com.google.android.setupcompat.util.WizardManagerHelper;
 
 /** A templatization layout with consistent style used in Setup Wizard or app itself. */
 public class PartnerCustomizationLayout extends TemplateLayout {
-  // Log tags can have at most 23 characters on N or before.
-  private static final String TAG = "PartnerCustomizedLayout";
+
+  private static final Logger LOG = new Logger("PartnerCustomizationLayout");
 
   /**
    * Attribute indicating whether usage of partner theme resources is allowed. This corresponds to
@@ -56,6 +57,18 @@
    */
   private boolean usePartnerResourceAttr;
 
+  /**
+   * Attribute indicating whether using full dynamic colors or not. This corresponds to the {@code
+   * app:sucFullDynamicColor} XML attribute.
+   */
+  private boolean useFullDynamicColorAttr;
+
+  /**
+   * Attribute indicating whether usage of dynamic is allowed. This corresponds to the existence of
+   * {@code app:sucFullDynamicColor} XML attribute.
+   */
+  private boolean useDynamicColor;
+
   private Activity activity;
 
   public PartnerCustomizationLayout(Context context) {
@@ -83,6 +96,9 @@
   }
 
   private void init(AttributeSet attrs, int defStyleAttr) {
+    if (isInEditMode()) {
+      return;
+    }
 
     TypedArray a =
         getContext()
@@ -132,15 +148,13 @@
   @Override
   protected void onBeforeTemplateInflated(AttributeSet attrs, int defStyleAttr) {
 
-    boolean isSetupFlow;
-
     // Sets default value to true since this timing
     // before PartnerCustomization members initialization
     usePartnerResourceAttr = true;
 
     activity = lookupActivityFromContext(getContext());
 
-    isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
+    boolean isSetupFlow = WizardManagerHelper.isAnySetupWizard(activity.getIntent());
 
     TypedArray a =
         getContext()
@@ -149,27 +163,32 @@
 
     if (!a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource)) {
       // TODO: Enable Log.WTF after other client already set sucUsePartnerResource.
-      Log.e(TAG, "Attribute sucUsePartnerResource not found in " + activity.getComponentName());
+      LOG.e("Attribute sucUsePartnerResource not found in " + activity.getComponentName());
     }
 
     usePartnerResourceAttr =
         isSetupFlow
             || a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucUsePartnerResource, true);
 
+    useDynamicColor = a.hasValue(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor);
+    useFullDynamicColorAttr =
+        a.getBoolean(R.styleable.SucPartnerCustomizationLayout_sucFullDynamicColor, false);
+
     a.recycle();
 
-    if (Log.isLoggable(TAG, Log.DEBUG)) {
-      Log.d(
-          TAG,
-          "activity="
-              + activity.getClass().getSimpleName()
-              + " isSetupFlow="
-              + isSetupFlow
-              + " enablePartnerResourceLoading="
-              + enablePartnerResourceLoading()
-              + " usePartnerResourceAttr="
-              + usePartnerResourceAttr);
-    }
+    LOG.atDebug(
+        "activity="
+            + activity.getClass().getSimpleName()
+            + " isSetupFlow="
+            + isSetupFlow
+            + " enablePartnerResourceLoading="
+            + enablePartnerResourceLoading()
+            + " usePartnerResourceAttr="
+            + usePartnerResourceAttr
+            + " useDynamicColor="
+            + useDynamicColor
+            + " useFullDynamicColorAttr="
+            + useFullDynamicColorAttr);
   }
 
   @Override
@@ -216,7 +235,7 @@
     }
   }
 
-  private static Activity lookupActivityFromContext(Context context) {
+  public static Activity lookupActivityFromContext(Context context) {
     if (context instanceof Activity) {
       return (Activity) context;
     } else if (context instanceof ContextWrapper) {
@@ -252,4 +271,30 @@
     }
     return true;
   }
+
+  /**
+   * Returns {@code true} if the current layout/activity applies dynamic color. Otherwise, returns
+   * {@code false}.
+   */
+  public boolean shouldApplyDynamicColor() {
+    if (!useDynamicColor) {
+      return false;
+    }
+    if (!BuildCompatUtils.isAtLeastS()) {
+      return false;
+    }
+    if (!PartnerConfigHelper.get(getContext()).isAvailable()) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns {@code true} if the current layout/activity applies full dynamic color. Otherwise,
+   * returns {@code false}. This method combines the result of {@link #shouldApplyDynamicColor()}
+   * and the value of the {@code app:sucFullDynamicColor}.
+   */
+  public boolean useFullDynamicColor() {
+    return shouldApplyDynamicColor() && useFullDynamicColorAttr;
+  }
 }
diff --git a/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java b/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java
index af17a62..574f614 100644
--- a/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java
+++ b/main/java/com/google/android/setupcompat/internal/FallbackThemeWrapper.java
@@ -18,8 +18,8 @@
 
 import android.content.Context;
 import android.content.res.Resources.Theme;
-import androidx.annotation.StyleRes;
 import android.view.ContextThemeWrapper;
+import androidx.annotation.StyleRes;
 
 /**
  * Same as {@link ContextThemeWrapper}, but the base context's theme attributes take precedence over
diff --git a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
index 39b50cf..5f8bf67 100644
--- a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
+++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
@@ -27,7 +27,9 @@
   private final PartnerConfig buttonIconConfig;
   private final PartnerConfig buttonTextColorConfig;
   private final PartnerConfig buttonTextSizeConfig;
+  private final PartnerConfig buttonMinHeightConfig;
   private final PartnerConfig buttonTextTypeFaceConfig;
+  private final PartnerConfig buttonTextStyleConfig;
   private final PartnerConfig buttonRadiusConfig;
   private final PartnerConfig buttonRippleColorAlphaConfig;
   private final int partnerTheme;
@@ -40,14 +42,18 @@
       PartnerConfig buttonIconConfig,
       PartnerConfig buttonTextColorConfig,
       PartnerConfig buttonTextSizeConfig,
+      PartnerConfig buttonMinHeightConfig,
       PartnerConfig buttonTextTypeFaceConfig,
+      PartnerConfig buttonTextStyleConfig,
       PartnerConfig buttonRadiusConfig,
       PartnerConfig buttonRippleColorAlphaConfig) {
     this.partnerTheme = partnerTheme;
 
     this.buttonTextColorConfig = buttonTextColorConfig;
     this.buttonTextSizeConfig = buttonTextSizeConfig;
+    this.buttonMinHeightConfig = buttonMinHeightConfig;
     this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig;
+    this.buttonTextStyleConfig = buttonTextStyleConfig;
     this.buttonBackgroundConfig = buttonBackgroundConfig;
     this.buttonDisableAlphaConfig = buttonDisableAlphaConfig;
     this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig;
@@ -80,6 +86,10 @@
     return buttonTextColorConfig;
   }
 
+  public PartnerConfig getButtonMinHeightConfig() {
+    return buttonMinHeightConfig;
+  }
+
   public PartnerConfig getButtonTextSizeConfig() {
     return buttonTextSizeConfig;
   }
@@ -88,6 +98,10 @@
     return buttonTextTypeFaceConfig;
   }
 
+  public PartnerConfig getButtonTextStyleConfig() {
+    return buttonTextStyleConfig;
+  }
+
   public PartnerConfig getButtonRadiusConfig() {
     return buttonRadiusConfig;
   }
@@ -105,15 +119,19 @@
     private PartnerConfig buttonIconConfig = null;
     private PartnerConfig buttonTextColorConfig = null;
     private PartnerConfig buttonTextSizeConfig = null;
+    private PartnerConfig buttonMinHeight = null;
     private PartnerConfig buttonTextTypeFaceConfig = null;
+    private PartnerConfig buttonTextStyleConfig = null;
     private PartnerConfig buttonRadiusConfig = null;
     private PartnerConfig buttonRippleColorAlphaConfig = null;
     private int partnerTheme;
 
     public Builder(FooterButton footerButton) {
       this.footerButton = footerButton;
-      // default partnerTheme should be the same as footerButton.getTheme();
-      this.partnerTheme = this.footerButton.getTheme();
+      if (this.footerButton != null) {
+        // default partnerTheme should be the same as footerButton.getTheme();
+        this.partnerTheme = this.footerButton.getTheme();
+      }
     }
 
     public Builder setButtonBackgroundConfig(PartnerConfig buttonBackgroundConfig) {
@@ -146,11 +164,21 @@
       return this;
     }
 
+    public Builder setButtonMinHeight(PartnerConfig buttonMinHeightConfig) {
+      this.buttonMinHeight = buttonMinHeightConfig;
+      return this;
+    }
+
     public Builder setTextTypeFaceConfig(PartnerConfig buttonTextTypeFaceConfig) {
       this.buttonTextTypeFaceConfig = buttonTextTypeFaceConfig;
       return this;
     }
 
+    public Builder setTextStyleConfig(PartnerConfig buttonTextStyleConfig) {
+      this.buttonTextStyleConfig = buttonTextStyleConfig;
+      return this;
+    }
+
     public Builder setButtonRadiusConfig(PartnerConfig buttonRadiusConfig) {
       this.buttonRadiusConfig = buttonRadiusConfig;
       return this;
@@ -175,7 +203,9 @@
           buttonIconConfig,
           buttonTextColorConfig,
           buttonTextSizeConfig,
+          buttonMinHeight,
           buttonTextTypeFaceConfig,
+          buttonTextStyleConfig,
           buttonRadiusConfig,
           buttonRippleColorAlphaConfig);
     }
diff --git a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
index 1197645..3b7d5a5 100644
--- a/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
+++ b/main/java/com/google/android/setupcompat/internal/PersistableBundles.java
@@ -22,16 +22,18 @@
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.util.ArrayMap;
-import android.util.Log;
+import com.google.android.setupcompat.util.Logger;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
 /** Contains utility methods related to {@link PersistableBundle}. */
-@TargetApi(VERSION_CODES.Q)
+@TargetApi(VERSION_CODES.LOLLIPOP_MR1)
 public final class PersistableBundles {
 
+  private static final Logger LOG = new Logger("PersistableBundles");
+
   /**
    * Merges two or more {@link PersistableBundle}. Ensures no conflict of keys occurred during
    * merge.
@@ -121,7 +123,7 @@
     for (String key : baseBundle.keySet()) {
       Object value = baseBundle.get(key);
       if (!isSupportedDataType(value)) {
-        Log.w(TAG, String.format("Unknown/unsupported data type [%s] for key %s", value, key));
+        LOG.w(String.format("Unknown/unsupported data type [%s] for key %s", value, key));
         continue;
       }
       map.put(key, baseBundle.get(key));
@@ -141,6 +143,4 @@
   private PersistableBundles() {
     throw new AssertionError("Should not be instantiated");
   }
-
-  private static final String TAG = "SetupCompat.PersistBls";
 }
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
index a1ca156..149da54 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
@@ -21,9 +21,9 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import androidx.annotation.VisibleForTesting;
-import android.util.Log;
 import com.google.android.setupcompat.ISetupCompatService;
 import com.google.android.setupcompat.logging.internal.SetupMetricsLoggingConstants.MetricType;
+import com.google.android.setupcompat.util.Logger;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.TimeUnit;
@@ -41,11 +41,14 @@
  */
 public class SetupCompatServiceInvoker {
 
+  private static final Logger LOG = new Logger("SetupCompatServiceInvoker");
+
+  @SuppressLint("DefaultLocale")
   public void logMetricEvent(@MetricType int metricType, Bundle args) {
     try {
       loggingExecutor.execute(() -> invokeLogMetric(metricType, args));
     } catch (RejectedExecutionException e) {
-      Log.e(TAG, String.format("Metric of type %d dropped since queue is full.", metricType), e);
+      LOG.e(String.format("Metric of type %d dropped since queue is full.", metricType), e);
     }
   }
 
@@ -53,7 +56,7 @@
     try {
       setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
     } catch (RejectedExecutionException e) {
-      Log.e(TAG, String.format("Screen %s bind back fail.", screenName), e);
+      LOG.e(String.format("Screen %s bind back fail.", screenName), e);
     }
   }
 
@@ -66,10 +69,10 @@
       if (setupCompatService != null) {
         setupCompatService.logMetric(metricType, args, Bundle.EMPTY);
       } else {
-        Log.w(TAG, "logMetric failed since service reference is null. Are the permissions valid?");
+        LOG.w("logMetric failed since service reference is null. Are the permissions valid?");
       }
-    } catch (InterruptedException | TimeoutException | RemoteException e) {
-      Log.e(TAG, String.format("Exception occurred while trying to log metric = [%s]", args), e);
+    } catch (InterruptedException | TimeoutException | RemoteException | IllegalStateException e) {
+      LOG.e(String.format("Exception occurred while trying to log metric = [%s]", args), e);
     }
   }
 
@@ -81,11 +84,10 @@
       if (setupCompatService != null) {
         setupCompatService.validateActivity(screenName, bundle);
       } else {
-        Log.w(TAG, "BindBack failed since service reference is null. Are the permissions valid?");
+        LOG.w("BindBack failed since service reference is null. Are the permissions valid?");
       }
     } catch (InterruptedException | TimeoutException | RemoteException e) {
-      Log.e(
-          TAG,
+      LOG.e(
           String.format("Exception occurred while %s trying bind back to SetupWizard.", screenName),
           e);
     }
@@ -125,5 +127,4 @@
   private static SetupCompatServiceInvoker instance;
 
   private static final long MAX_WAIT_TIME_FOR_CONNECTION_MS = TimeUnit.SECONDS.toMillis(10);
-  private static final String TAG = "SucServiceInvoker";
 }
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
index 2043a81..e75d991 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
@@ -26,8 +26,8 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import android.util.Log;
 import com.google.android.setupcompat.ISetupCompatService;
+import com.google.android.setupcompat.util.Logger;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -40,6 +40,8 @@
  */
 public class SetupCompatServiceProvider {
 
+  private static final Logger LOG = new Logger("SetupCompatServiceProvider");
+
   /**
    * Returns an instance of {@link ISetupCompatService} if one already exists. If not, attempts to
    * rebind if the current state allows such an operation and waits until {@code waitTime} for
@@ -94,7 +96,7 @@
     }
 
     CountDownLatch connectedStateLatch = getConnectedCondition();
-    Log.i(TAG, "Waiting for service to get connected");
+    LOG.atInfo("Waiting for service to get connected");
     boolean stateChanged = connectedStateLatch.await(timeout, timeUnit);
     if (!stateChanged) {
       // Even though documentation states that disconnected service should connect again,
@@ -104,13 +106,10 @@
           String.format("Failed to acquire connection after [%s %s]", timeout, timeUnit));
     }
     currentServiceState = getCurrentServiceState();
-    if (Log.isLoggable(TAG, Log.INFO)) {
-      Log.i(
-          TAG,
-          String.format(
-              "Finished waiting for service to get connected. Current state = %s",
-              currentServiceState.state));
-    }
+    LOG.atInfo(
+        String.format(
+            "Finished waiting for service to get connected. Current state = %s",
+            currentServiceState.state));
     return currentServiceState.compatService;
   }
 
@@ -126,11 +125,11 @@
   private synchronized void requestServiceBind() {
     ServiceContext currentServiceState = getCurrentServiceState();
     if (currentServiceState.state == State.CONNECTED) {
-      Log.i(TAG, "Refusing to rebind since current state is already connected");
+      LOG.atInfo("Refusing to rebind since current state is already connected");
       return;
     }
     if (currentServiceState.state != State.NOT_STARTED) {
-      Log.i(TAG, "Unbinding existing service connection.");
+      LOG.atInfo("Unbinding existing service connection.");
       context.unbindService(serviceConnection);
     }
 
@@ -139,7 +138,7 @@
       bindAllowed =
           context.bindService(COMPAT_SERVICE_INTENT, serviceConnection, Context.BIND_AUTO_CREATE);
     } catch (SecurityException e) {
-      Log.e(TAG, "Unable to bind to compat service", e);
+      LOG.e("Unable to bind to compat service. " + e);
       bindAllowed = false;
     }
 
@@ -149,12 +148,12 @@
       // in the normal world
       if (getCurrentState() != State.CONNECTED) {
         swapServiceContextAndNotify(new ServiceContext(State.BINDING));
-        Log.i(TAG, "Context#bindService went through, now waiting for service connection");
+        LOG.atInfo("Context#bindService went through, now waiting for service connection");
       }
     } else {
       // SetupWizard is not installed/calling app does not have permissions to bind.
       swapServiceContextAndNotify(new ServiceContext(State.BIND_FAILED));
-      Log.e(TAG, "Context#bindService did not succeed.");
+      LOG.e("Context#bindService did not succeed.");
     }
   }
 
@@ -174,12 +173,9 @@
   }
 
   private void swapServiceContextAndNotify(ServiceContext latestServiceContext) {
-    if (Log.isLoggable(TAG, Log.INFO)) {
-      Log.i(
-          TAG,
-          String.format(
-              "State changed: %s -> %s", serviceContext.state, latestServiceContext.state));
-    }
+    LOG.atInfo(
+        String.format("State changed: %s -> %s", serviceContext.state, latestServiceContext.state));
+
     serviceContext = latestServiceContext;
     CountDownLatch countDownLatch = getAndClearConnectedCondition();
     if (countDownLatch != null) {
@@ -221,7 +217,7 @@
           State state = State.CONNECTED;
           if (binder == null) {
             state = State.DISCONNECTED;
-            Log.w(TAG, "Binder is null when onServiceConnected was called!");
+            LOG.w("Binder is null when onServiceConnected was called!");
           }
           swapServiceContextAndNotify(
               new ServiceContext(state, ISetupCompatService.Stub.asInterface(binder)));
@@ -336,6 +332,4 @@
   // lint error.
   @SuppressLint("StaticFieldLeak")
   private static volatile SetupCompatServiceProvider instance;
-
-  private static final String TAG = "SucServiceProvider";
 }
diff --git a/main/java/com/google/android/setupcompat/internal/TemplateLayout.java b/main/java/com/google/android/setupcompat/internal/TemplateLayout.java
index 34179d6..25a3c5b 100644
--- a/main/java/com/google/android/setupcompat/internal/TemplateLayout.java
+++ b/main/java/com/google/android/setupcompat/internal/TemplateLayout.java
@@ -20,15 +20,15 @@
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Build.VERSION_CODES;
-import androidx.annotation.Keep;
-import androidx.annotation.LayoutRes;
-import androidx.annotation.StyleRes;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.widget.FrameLayout;
+import androidx.annotation.Keep;
+import androidx.annotation.LayoutRes;
+import androidx.annotation.StyleRes;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.template.Mixin;
 import java.util.HashMap;
diff --git a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
index 7d0b731..2aa1240 100644
--- a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
+++ b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
@@ -30,24 +30,28 @@
 @TargetApi(VERSION_CODES.Q)
 public class PartnerCustomizedResourceListMetric {
 
-  public static void logMetrics(Context context, String screenName, Bundle bundle) {
+  public static void logMetrics(Context context, String deviceDisplayName, Bundle bundle) {
     PersistableBundle logBundle =
-        buildLogBundleFromResourceConfigBundle(context.getPackageName(), bundle);
+        buildLogBundleFromResourceConfigBundle(context.getPackageName(), deviceDisplayName, bundle);
     if (!logBundle.isEmpty()) {
       SetupMetricsLogger.logCustomEvent(
           context,
-          CustomEvent.create(MetricKey.get("PartnerCustomizationResource", screenName), logBundle));
+          CustomEvent.create(
+              MetricKey.get("PartnerCustomizationResource", "NoScreenName"), logBundle));
     }
   }
 
   @VisibleForTesting
   public static PersistableBundle buildLogBundleFromResourceConfigBundle(
-      String defaultPackageName, Bundle resourceConfigBundle) {
+      String defaultPackageName, String deviceDisplayName, Bundle resourceConfigBundle) {
     PersistableBundle persistableBundle = new PersistableBundle();
+    persistableBundle.putString("deviceDisplayName", deviceDisplayName);
     for (String key : resourceConfigBundle.keySet()) {
       Bundle resourceExtra = resourceConfigBundle.getBundle(key);
       if (!resourceExtra.getString("packageName", defaultPackageName).equals(defaultPackageName)) {
         persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), true);
+      } else {
+        persistableBundle.putBoolean(resourceExtra.getString("resourceName", key), false);
       }
     }
 
diff --git a/main/java/com/google/android/setupcompat/portal/NotificationComponent.java b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java
new file mode 100644
index 0000000..a90963b
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/NotificationComponent.java
@@ -0,0 +1,114 @@
+/*
+ * 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.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.IntDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A class that represents how a persistent notification is to be presented to the user using the
+ * {@link com.google.android.setupcompat.portal.ISetupNotificationService}.
+ */
+public class NotificationComponent implements Parcelable {
+
+  @NotificationType private final int notificationType;
+  private Bundle extraBundle = new Bundle();
+
+  private NotificationComponent(@NotificationType int notificationType) {
+    this.notificationType = notificationType;
+  }
+
+  protected NotificationComponent(Parcel in) {
+    this(in.readInt());
+    extraBundle = in.readBundle(Bundle.class.getClassLoader());
+  }
+
+  public int getIntExtra(String key, int defValue) {
+    return extraBundle.getInt(key, defValue);
+  }
+
+  @NotificationType
+  public int getNotificationType() {
+    return notificationType;
+  }
+
+  @Override
+  public void writeToParcel(Parcel dest, int flags) {
+    dest.writeInt(notificationType);
+    dest.writeBundle(extraBundle);
+  }
+
+  @Override
+  public int describeContents() {
+    return 0;
+  }
+
+  public static final Creator<NotificationComponent> CREATOR =
+      new Creator<NotificationComponent>() {
+        @Override
+        public NotificationComponent createFromParcel(Parcel in) {
+          return new NotificationComponent(in);
+        }
+
+        @Override
+        public NotificationComponent[] newArray(int size) {
+          return new NotificationComponent[size];
+        }
+      };
+
+  @Retention(RetentionPolicy.SOURCE)
+  @IntDef({
+    NotificationType.INITIAL_ONGOING,
+    NotificationType.PREDEFERRED,
+    NotificationType.PREDEFERRED_PREPARING,
+    NotificationType.DEFERRED,
+    NotificationType.DEFERRED_ONGOING,
+    NotificationType.PORTAL
+  })
+  public @interface NotificationType {
+    int UNKNOWN = 0;
+    int INITIAL_ONGOING = 1;
+    int PREDEFERRED = 2;
+    int PREDEFERRED_PREPARING = 3;
+    int DEFERRED = 4;
+    int DEFERRED_ONGOING = 5;
+    int PORTAL = 6;
+    int MAX = 7;
+  }
+
+  public static class Builder {
+
+    private final NotificationComponent component;
+
+    public Builder(@NotificationType int notificationType) {
+      component = new NotificationComponent(notificationType);
+    }
+
+    public Builder putIntExtra(String key, int value) {
+      component.extraBundle.putInt(key, value);
+      return this;
+    }
+
+    public NotificationComponent build() {
+      return component;
+    }
+  }
+}
diff --git a/main/java/com/google/android/setupcompat/portal/PortalConstants.java b/main/java/com/google/android/setupcompat/portal/PortalConstants.java
new file mode 100644
index 0000000..52d8700
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalConstants.java
@@ -0,0 +1,72 @@
+/*
+ * 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 androidx.annotation.IntDef;
+import androidx.annotation.StringDef;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Constant values used for Portal */
+public class PortalConstants {
+
+  /** Enumeration of pending reasons, for {@link IPortalProgressCallback#setPendingReason}. */
+  @Retention(RetentionPolicy.SOURCE)
+  @IntDef({
+    PendingReason.IN_PROGRESS,
+    PendingReason.PROGRESS_REQUEST_ANY_NETWORK,
+    PendingReason.PROGRESS_REQUEST_WIFI,
+    PendingReason.PROGRESS_REQUEST_MOBILE,
+    PendingReason.PROGRESS_RETRY,
+    PendingReason.PROGRESS_REQUEST_REMOVED,
+    PendingReason.MAX
+  })
+  public @interface PendingReason {
+    /**
+     * Don't used this, use {@link IPortalProgressCallback#setProgressCount} ot {@link
+     * IPortalProgressCallback#setProgressPercentage} will reset pending reason to in progress.
+     */
+    int IN_PROGRESS = 0;
+
+    /** Clients required network. */
+    int PROGRESS_REQUEST_ANY_NETWORK = 1;
+
+    /** Clients required a wifi network. */
+    int PROGRESS_REQUEST_WIFI = 2;
+
+    /** Client required a mobile data */
+    int PROGRESS_REQUEST_MOBILE = 3;
+
+    /** Client needs to wait for retry */
+    int PROGRESS_RETRY = 4;
+
+    /** Client required to remove added task */
+    int PROGRESS_REQUEST_REMOVED = 5;
+
+    int MAX = 6;
+  }
+
+  /** Bundle keys used in {@link IPortalProgressService#onGetRemainingValues}. */
+  @Retention(RetentionPolicy.SOURCE)
+  @StringDef({RemainingValues.REMAINING_SIZE_TO_BE_DOWNLOAD_IN_KB})
+  public @interface RemainingValues {
+    /** Remaining size to download in MB. */
+    String REMAINING_SIZE_TO_BE_DOWNLOAD_IN_KB = "RemainingSizeInKB";
+  }
+
+  private PortalConstants() {}
+}
diff --git a/main/java/com/google/android/setupcompat/portal/PortalHelper.java b/main/java/com/google/android/setupcompat/portal/PortalHelper.java
new file mode 100644
index 0000000..4d1965a
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalHelper.java
@@ -0,0 +1,301 @@
+/*
+ * 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.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import androidx.annotation.NonNull;
+import com.google.android.setupcompat.internal.Preconditions;
+import com.google.android.setupcompat.portal.PortalConstants.RemainingValues;
+import com.google.android.setupcompat.util.Logger;
+
+/** This class is responsible for safely executing methods on SetupNotificationService. */
+public class PortalHelper {
+
+  private static final Logger LOG = new Logger("PortalHelper");
+
+  public static final String EXTRA_KEY_IS_SETUP_WIZARD = "isSetupWizard";
+
+  public static final String ACTION_BIND_SETUP_NOTIFICATION_SERVICE =
+      "com.google.android.setupcompat.portal.SetupNotificationService.BIND";
+
+  public static final String RESULT_BUNDLE_KEY_RESULT = "Result";
+  public static final String RESULT_BUNDLE_KEY_ERROR = "Error";
+  public static final String RESULT_BUNDLE_KEY_PORTAL_NOTIFICATION_AVAILABLE =
+      "PortalNotificationAvailable";
+
+  public static final Intent NOTIFICATION_SERVICE_INTENT =
+      new Intent(ACTION_BIND_SETUP_NOTIFICATION_SERVICE)
+          .setPackage("com.google.android.setupwizard");
+
+  /**
+   * Binds SetupNotificationService. For more detail see {@link Context#bindService(Intent,
+   * ServiceConnection, int)}
+   */
+  public static boolean bindSetupNotificationService(
+      @NonNull Context context, @NonNull ServiceConnection connection) {
+    Preconditions.checkNotNull(context, "Context cannot be null");
+    Preconditions.checkNotNull(connection, "ServiceConnection cannot be null");
+    try {
+      return context.bindService(NOTIFICATION_SERVICE_INTENT, connection, Context.BIND_AUTO_CREATE);
+    } catch (SecurityException e) {
+      LOG.e("Exception occurred while binding SetupNotificationService", e);
+      return false;
+    }
+  }
+
+  /**
+   * Registers a progress service to SUW service. The function response for bind service and invoke
+   * function safely, and returns the result using {@link RegisterCallback}.
+   *
+   * @param context The application context.
+   * @param component Identifies the progress service to execute.
+   * @param callback Receives register result. {@link RegisterCallback#onSuccess} called while
+   *     register succeed. {@link RegisterCallback#onFailure} called while register failed.
+   */
+  public static void registerProgressService(
+      @NonNull Context context,
+      @NonNull ProgressServiceComponent component,
+      @NonNull RegisterCallback callback) {
+    Preconditions.checkNotNull(context, "Context cannot be null");
+    Preconditions.checkNotNull(component, "ProgressServiceComponent cannot be null");
+    Preconditions.checkNotNull(callback, "RegisterCallback cannot be null");
+
+    ServiceConnection connection =
+        new ServiceConnection() {
+          @Override
+          public void onServiceConnected(ComponentName name, IBinder binder) {
+            if (binder != null) {
+              ISetupNotificationService service =
+                  ISetupNotificationService.Stub.asInterface(binder);
+              try {
+                if (VERSION.SDK_INT >= VERSION_CODES.N) {
+                  final ServiceConnection serviceConnection = this;
+                  service.registerProgressService(
+                      component,
+                      getCurrentUserHandle(),
+                      new IPortalRegisterResultListener.Stub() {
+                        @Override
+                        public void onResult(Bundle result) {
+                          if (result.getBoolean(RESULT_BUNDLE_KEY_RESULT, false)) {
+                            callback.onSuccess(
+                                result.getBoolean(
+                                    RESULT_BUNDLE_KEY_PORTAL_NOTIFICATION_AVAILABLE, false));
+                          } else {
+                            callback.onFailure(
+                                new IllegalStateException(
+                                    result.getString(RESULT_BUNDLE_KEY_ERROR, "Unknown error")));
+                          }
+                          context.unbindService(serviceConnection);
+                        }
+                      });
+                } else {
+                  callback.onFailure(
+                      new IllegalStateException(
+                          "SetupNotificationService is not supported before Android N"));
+                  context.unbindService(this);
+                }
+              } catch (RemoteException | NullPointerException e) {
+                callback.onFailure(e);
+                context.unbindService(this);
+              }
+            } else {
+              callback.onFailure(
+                  new IllegalStateException("SetupNotification should not return null binder"));
+              context.unbindService(this);
+            }
+          }
+
+          @Override
+          public void onServiceDisconnected(ComponentName name) {
+            // Do nothing when service disconnected
+          }
+        };
+
+    if (!bindSetupNotificationService(context, connection)) {
+      LOG.e("Failed to bind SetupNotificationService.");
+      callback.onFailure(new SecurityException("Failed to bind SetupNotificationService."));
+    }
+  }
+
+  public static void isPortalAvailable(
+      @NonNull Context context, @NonNull final PortalAvailableResultListener listener) {
+    ServiceConnection connection =
+        new ServiceConnection() {
+          @Override
+          public void onServiceConnected(ComponentName name, IBinder binder) {
+            if (binder != null) {
+              ISetupNotificationService service =
+                  ISetupNotificationService.Stub.asInterface(binder);
+
+              try {
+                listener.onResult(service.isPortalAvailable());
+              } catch (RemoteException e) {
+                LOG.e("Failed to invoke SetupNotificationService#isPortalAvailable");
+                listener.onResult(false);
+              }
+            }
+            context.unbindService(this);
+          }
+
+          @Override
+          public void onServiceDisconnected(ComponentName name) {}
+        };
+
+    if (!bindSetupNotificationService(context, connection)) {
+      LOG.e(
+          "Failed to bind SetupNotificationService. Do you have permission"
+              + " \"com.google.android.setupwizard.SETUP_PROGRESS_SERVICE\"");
+      listener.onResult(false);
+    }
+  }
+
+  public static void isProgressServiceAlive(
+      @NonNull final Context context,
+      @NonNull final ProgressServiceComponent component,
+      @NonNull final ProgressServiceAliveResultListener listener) {
+    Preconditions.checkNotNull(context, "Context cannot be null");
+    Preconditions.checkNotNull(component, "ProgressServiceComponent cannot be null");
+    Preconditions.checkNotNull(listener, "ProgressServiceAliveResultCallback cannot be null");
+
+    ServiceConnection connection =
+        new ServiceConnection() {
+          @Override
+          public void onServiceConnected(ComponentName name, IBinder binder) {
+            if (binder != null) {
+              ISetupNotificationService service =
+                  ISetupNotificationService.Stub.asInterface(binder);
+
+              try {
+                if (VERSION.SDK_INT >= VERSION_CODES.N) {
+                  listener.onResult(
+                      service.isProgressServiceAlive(component, getCurrentUserHandle()));
+                } else {
+                  listener.onResult(false);
+                }
+
+              } catch (RemoteException e) {
+                LOG.w("Failed to invoke SetupNotificationService#isProgressServiceAlive");
+                listener.onResult(false);
+              }
+            }
+            context.unbindService(this);
+          }
+
+          @Override
+          public void onServiceDisconnected(ComponentName name) {}
+        };
+
+    if (!bindSetupNotificationService(context, connection)) {
+      LOG.e(
+          "Failed to bind SetupNotificationService. Do you have permission"
+              + " \"com.google.android.setupwizard.SETUP_PROGRESS_SERVICE\"");
+      listener.onResult(false);
+    }
+  }
+
+  private static UserHandle getCurrentUserHandle() {
+    if (VERSION.SDK_INT >= VERSION_CODES.N) {
+      return UserHandle.getUserHandleForUid(Process.myUid());
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Creates the {@code Bundle} including the bind progress service result.
+   *
+   * @param succeed whether bind service success or not.
+   * @param errorMsg describe the reason why bind service failed.
+   * @return A bundle include bind result and error message.
+   */
+  public static Bundle createResultBundle(
+      boolean succeed, String errorMsg, boolean isPortalNotificationAvailable) {
+    Bundle bundle = new Bundle();
+    bundle.putBoolean(RESULT_BUNDLE_KEY_RESULT, succeed);
+    if (!succeed) {
+      bundle.putString(RESULT_BUNDLE_KEY_ERROR, errorMsg);
+    }
+    bundle.putBoolean(
+        RESULT_BUNDLE_KEY_PORTAL_NOTIFICATION_AVAILABLE, isPortalNotificationAvailable);
+    return bundle;
+  }
+
+  /**
+   * Returns {@code true}, if the intent is bound from SetupWizard, otherwise returns false.
+   *
+   * @param intent that received when onBind.
+   */
+  public static boolean isFromSUW(Intent intent) {
+    return intent != null && intent.getBooleanExtra(EXTRA_KEY_IS_SETUP_WIZARD, false);
+  }
+
+  /** A callback for accepting the results of SetupNotificationService. */
+  public interface RegisterCallback {
+    void onSuccess(boolean isPortalNow);
+
+    void onFailure(Throwable throwable);
+  }
+
+  public interface RegisterNotificationCallback {
+    void onSuccess();
+
+    void onFailure(Throwable throwable);
+  }
+
+  public interface ProgressServiceAliveResultListener {
+    void onResult(boolean isAlive);
+  }
+
+  public interface PortalAvailableResultListener {
+    void onResult(boolean isAvailable);
+  }
+
+  public static class RemainingValueBuilder {
+    private final Bundle bundle = new Bundle();
+
+    public static RemainingValueBuilder createBuilder() {
+      return new RemainingValueBuilder();
+    }
+
+    public RemainingValueBuilder setRemainingSizeInKB(int size) {
+      Preconditions.checkArgument(
+          size >= 0, "The remainingSize should be positive integer or zero.");
+      bundle.putInt(RemainingValues.REMAINING_SIZE_TO_BE_DOWNLOAD_IN_KB, size);
+      return this;
+    }
+
+    public Bundle build() {
+      return bundle;
+    }
+
+    private RemainingValueBuilder() {}
+  }
+
+  private PortalHelper() {}
+}
+
+
diff --git a/main/java/com/google/android/setupcompat/portal/PortalResultHelper.java b/main/java/com/google/android/setupcompat/portal/PortalResultHelper.java
new file mode 100644
index 0000000..cec2990
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/PortalResultHelper.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.os.Bundle;
+
+public class PortalResultHelper {
+
+  public static final String RESULT_BUNDLE_KEY_RESULT = "Result";
+  public static final String RESULT_BUNDLE_KEY_ERROR = "Error";
+
+  public static boolean isSuccess(Bundle bundle) {
+    return bundle.getBoolean(RESULT_BUNDLE_KEY_RESULT, false);
+  }
+
+  public static String getErrorMessage(Bundle bundle) {
+    return bundle.getString(RESULT_BUNDLE_KEY_ERROR, null);
+  }
+
+  public static Bundle createSuccessBundle() {
+    Bundle resultBundle = new Bundle();
+    resultBundle.putBoolean(RESULT_BUNDLE_KEY_RESULT, true);
+    return resultBundle;
+  }
+
+  public static Bundle createFailureBundle(String errorMessage) {
+    Bundle resultBundle = new Bundle();
+    resultBundle.putBoolean(RESULT_BUNDLE_KEY_RESULT, false);
+    resultBundle.putString(RESULT_BUNDLE_KEY_ERROR, errorMessage);
+    return resultBundle;
+  }
+
+  private PortalResultHelper() {}
+  ;
+}
diff --git a/main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java b/main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java
new file mode 100644
index 0000000..be11239
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/portal/ProgressServiceComponent.java
@@ -0,0 +1,250 @@
+/*
+ * 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 progress service to be registered to {@link
+ * com.google.android.setupcompat.portal.ISetupNotificationService}.
+ */
+public class ProgressServiceComponent implements Parcelable {
+  private final String packageName;
+  private final String taskName;
+  private final boolean isSilent;
+  private final boolean autoRebind;
+  private final long timeoutForReRegister;
+  @StringRes private final int displayNameResId;
+  @DrawableRes private final int displayIconResId;
+  private final Intent serviceIntent;
+  private final Intent itemClickIntent;
+
+  private ProgressServiceComponent(
+      String packageName,
+      String taskName,
+      boolean isSilent,
+      boolean autoRebind,
+      long timeoutForReRegister,
+      @StringRes int displayNameResId,
+      @DrawableRes int displayIconResId,
+      Intent serviceIntent,
+      Intent itemClickIntent) {
+    this.packageName = packageName;
+    this.taskName = taskName;
+    this.isSilent = isSilent;
+    this.autoRebind = autoRebind;
+    this.timeoutForReRegister = timeoutForReRegister;
+    this.displayNameResId = displayNameResId;
+    this.displayIconResId = displayIconResId;
+    this.serviceIntent = serviceIntent;
+    this.itemClickIntent = itemClickIntent;
+  }
+
+  /** Returns a new instance of {@link Builder}. */
+  public static Builder newBuilder() {
+    return new ProgressServiceComponent.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 whether the service is silent or not */
+  public boolean isSilent() {
+    return isSilent;
+  }
+
+  /** Auto rebind progress service while service connection disconnect. Default: true */
+  public boolean isAutoRebind() {
+    return autoRebind;
+  }
+
+  /** The timeout period waiting for client register progress service again. */
+  public long getTimeoutForReRegister() {
+    return timeoutForReRegister;
+  }
+
+  /** 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 used to bind progress service. */
+  public Intent getServiceIntent() {
+    return serviceIntent;
+  }
+
+  /** 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(isSilent() ? 1 : 0);
+    dest.writeInt(getDisplayName());
+    dest.writeInt(getDisplayIcon());
+    dest.writeParcelable(getServiceIntent(), 0);
+    dest.writeParcelable(getItemClickIntent(), 0);
+    dest.writeInt(isAutoRebind() ? 1 : 0);
+    dest.writeLong(getTimeoutForReRegister());
+  }
+
+  @Override
+  public int describeContents() {
+    return 0;
+  }
+
+  public static final Creator<ProgressServiceComponent> CREATOR =
+      new Creator<ProgressServiceComponent>() {
+        @Override
+        public ProgressServiceComponent createFromParcel(Parcel in) {
+          return ProgressServiceComponent.newBuilder()
+              .setPackageName(in.readString())
+              .setTaskName(in.readString())
+              .setSilentMode(in.readInt() == 1)
+              .setDisplayName(in.readInt())
+              .setDisplayIcon(in.readInt())
+              .setServiceIntent(in.readParcelable(Intent.class.getClassLoader()))
+              .setItemClickIntent(in.readParcelable(Intent.class.getClassLoader()))
+              .setAutoRebind(in.readInt() == 1)
+              .setTimeoutForReRegister(in.readLong())
+              .build();
+        }
+
+        @Override
+        public ProgressServiceComponent[] newArray(int size) {
+          return new ProgressServiceComponent[size];
+        }
+      };
+
+  /** Builder class for {@link ProgressServiceComponent} objects */
+  public static class Builder {
+    private String packageName;
+    private String taskName;
+    private boolean isSilent = false;
+    private boolean autoRebind = true;
+    private long timeoutForReRegister = 0L;
+    @StringRes private int displayNameResId;
+    @DrawableRes private int displayIconResId;
+    private Intent serviceIntent;
+    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 service as silent mode, it executes without UI on PortalActivity. */
+    public Builder setSilentMode(boolean isSilent) {
+      this.isSilent = isSilent;
+      return this;
+    }
+
+    /** Sets the service need auto rebind or not when service connection disconnected. */
+    public Builder setAutoRebind(boolean autoRebind) {
+      this.autoRebind = autoRebind;
+      return this;
+    }
+
+    /**
+     * Sets the timeout period waiting for the client register again, only works when auto-rebind
+     * disabled. When 0 is set, will read default configuration from SUW.
+     */
+    public Builder setTimeoutForReRegister(long timeoutForReRegister) {
+      this.timeoutForReRegister = timeoutForReRegister;
+      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 setServiceIntent(Intent serviceIntent) {
+      this.serviceIntent = serviceIntent;
+      return this;
+    }
+
+    public Builder setItemClickIntent(Intent itemClickIntent) {
+      this.itemClickIntent = itemClickIntent;
+      return this;
+    }
+
+    public ProgressServiceComponent build() {
+      Preconditions.checkNotNull(packageName, "packageName cannot be null.");
+      Preconditions.checkNotNull(taskName, "serviceClass cannot be null.");
+      Preconditions.checkNotNull(serviceIntent, "Service intent cannot be null.");
+      Preconditions.checkNotNull(itemClickIntent, "Item click intent cannot be null");
+      if (!isSilent) {
+        Preconditions.checkArgument(
+            displayNameResId != 0, "Invalidate resource id of display name");
+        Preconditions.checkArgument(
+            displayIconResId != 0, "Invalidate resource id of display icon");
+      }
+      return new ProgressServiceComponent(
+          packageName,
+          taskName,
+          isSilent,
+          autoRebind,
+          timeoutForReRegister,
+          displayNameResId,
+          displayIconResId,
+          serviceIntent,
+          itemClickIntent);
+    }
+
+    private Builder() {}
+  }
+}
diff --git a/main/java/com/google/android/setupcompat/template/FooterActionButton.java b/main/java/com/google/android/setupcompat/template/FooterActionButton.java
index bb26d19..86a06d9 100644
--- a/main/java/com/google/android/setupcompat/template/FooterActionButton.java
+++ b/main/java/com/google/android/setupcompat/template/FooterActionButton.java
@@ -18,11 +18,11 @@
 
 import android.annotation.SuppressLint;
 import android.content.Context;
-import androidx.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.Button;
+import androidx.annotation.Nullable;
 
 /** Button that can react to touch when disabled. */
 public class FooterActionButton extends Button {
diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index bc9e5c1..b75d972 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -24,16 +24,17 @@
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Color;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.Typeface;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.InsetDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.RippleDrawable;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.PersistableBundle;
+import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
 import androidx.annotation.AttrRes;
 import androidx.annotation.CallSuper;
 import androidx.annotation.ColorInt;
@@ -44,25 +45,15 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
-import android.util.AttributeSet;
-import android.util.StateSet;
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewStub;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.LinearLayout.LayoutParams;
 import com.google.android.setupcompat.PartnerCustomizationLayout;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
-import com.google.android.setupcompat.internal.Preconditions;
 import com.google.android.setupcompat.internal.TemplateLayout;
 import com.google.android.setupcompat.logging.internal.FooterBarMixinMetrics;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
 import com.google.android.setupcompat.template.FooterButton.ButtonType;
+import java.util.Locale;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -77,6 +68,8 @@
   @Nullable private final ViewStub footerStub;
 
   @VisibleForTesting final boolean applyPartnerResources;
+  @VisibleForTesting final boolean applyDynamicColor;
+  @VisibleForTesting final boolean useFullDynamicColor;
 
   private LinearLayout buttonContainer;
   private FooterButton primaryButton;
@@ -94,8 +87,8 @@
   @ColorInt private final int footerBarPrimaryBackgroundColor;
   @ColorInt private final int footerBarSecondaryBackgroundColor;
   private boolean removeFooterBarWhenEmpty = true;
+  private boolean isSecondaryButtonInPrimaryStyle = false;
 
-  private static final float DEFAULT_DISABLED_ALPHA = 0.26f;
   private static final AtomicInteger nextGeneratedId = new AtomicInteger(1);
 
   @VisibleForTesting public final FooterBarMixinMetrics metrics = new FooterBarMixinMetrics();
@@ -110,10 +103,10 @@
           Button button = buttonContainer.findViewById(id);
           if (button != null) {
             button.setEnabled(enabled);
-            if (applyPartnerResources) {
-              updateButtonTextColorWithPartnerConfig(
+            if (applyPartnerResources && !applyDynamicColor) {
+              updateButtonTextColorWithEnabledState(
                   button,
-                  (id == primaryButtonId)
+                  (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
                       ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
                       : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR);
             }
@@ -141,6 +134,25 @@
           }
         }
       }
+
+      @Override
+      @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
+      public void onLocaleChanged(Locale locale) {
+        if (buttonContainer != null) {
+          Button button = buttonContainer.findViewById(id);
+          if (button != null && locale != null) {
+            button.setTextLocale(locale);
+          }
+        }
+      }
+
+      @Override
+      @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
+      public void onDirectionChanged(int direction) {
+        if (buttonContainer != null && direction != -1) {
+          buttonContainer.setLayoutDirection(direction);
+        }
+      }
     };
   }
 
@@ -159,6 +171,14 @@
         layout instanceof PartnerCustomizationLayout
             && ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource();
 
+    applyDynamicColor =
+        layout instanceof PartnerCustomizationLayout
+            && ((PartnerCustomizationLayout) layout).shouldApplyDynamicColor();
+
+    useFullDynamicColor =
+        layout instanceof PartnerCustomizationLayout
+            && ((PartnerCustomizationLayout) layout).useFullDynamicColor();
+
     TypedArray a =
         context.obtainStyledAttributes(attrs, R.styleable.SucFooterBarMixin, defStyleAttr, 0);
     defaultPadding =
@@ -253,11 +273,14 @@
       return;
     }
 
-    @ColorInt
-    int color =
-        PartnerConfigHelper.get(context)
-            .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR);
-    buttonContainer.setBackgroundColor(color);
+    // skip apply partner resources on footerbar background if dynamic color enabled
+    if (!useFullDynamicColor) {
+      @ColorInt
+      int color =
+          PartnerConfigHelper.get(context)
+              .getColor(context, PartnerConfig.CONFIG_FOOTER_BAR_BG_COLOR);
+      buttonContainer.setBackgroundColor(color);
+    }
 
     footerBarPaddingTop =
         (int)
@@ -273,6 +296,17 @@
         footerBarPaddingTop,
         buttonContainer.getPaddingRight(),
         footerBarPaddingBottom);
+
+    if (PartnerConfigHelper.get(context)
+        .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT)) {
+      int minHeight =
+          (int)
+              PartnerConfigHelper.get(context)
+                  .getDimension(context, PartnerConfig.CONFIG_FOOTER_BAR_MIN_HEIGHT);
+      if (minHeight > 0) {
+        buttonContainer.setMinimumHeight(minHeight);
+      }
+    }
   }
 
   /**
@@ -310,7 +344,9 @@
             .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
             .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR)
             .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+            .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
             .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+            .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
             .build();
 
     FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
@@ -346,7 +382,14 @@
   /** Sets secondary button for footer. */
   @MainThread
   public void setSecondaryButton(FooterButton footerButton) {
+    setSecondaryButton(footerButton, /*usePrimaryStyle= */ false);
+  }
+
+  /** Sets secondary button for footer. Allow to use the primary button style. */
+  @MainThread
+  public void setSecondaryButton(FooterButton footerButton, boolean usePrimaryStyle) {
     ensureOnMainThread("setSecondaryButton");
+    isSecondaryButtonInPrimaryStyle = usePrimaryStyle;
     ensureFooterInflated();
 
     // Setup button partner config
@@ -355,18 +398,29 @@
             .setPartnerTheme(
                 getPartnerTheme(
                     footerButton,
-                    /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Secondary,
-                    /* buttonBackgroundColorConfig= */ PartnerConfig
-                        .CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR))
-            .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
+                    /* defaultPartnerTheme= */ usePrimaryStyle
+                        ? R.style.SucPartnerCustomizationButton_Primary
+                        : R.style.SucPartnerCustomizationButton_Secondary,
+                    /* buttonBackgroundColorConfig= */ usePrimaryStyle
+                        ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR
+                        : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR))
+            .setButtonBackgroundConfig(
+                usePrimaryStyle
+                    ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR
+                    : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
             .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
             .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
             .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType()))
             .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
             .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
-            .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
+            .setTextColorConfig(
+                usePrimaryStyle
+                    ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
+                    : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
             .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+            .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
             .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+            .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
             .build();
 
     FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
@@ -395,6 +449,16 @@
     buttonContainer.removeAllViews();
 
     if (tempSecondaryButton != null) {
+      if (isSecondaryButtonInPrimaryStyle) {
+        // Since the secondary button has the same style (with background) as the primary button,
+        // we need to have the left padding equal to the right padding.
+        updateFooterBarPadding(
+            buttonContainer,
+            buttonContainer.getPaddingRight(),
+            buttonContainer.getPaddingTop(),
+            buttonContainer.getPaddingRight(),
+            buttonContainer.getPaddingBottom());
+      }
       buttonContainer.addView(tempSecondaryButton);
     }
     addSpace();
@@ -411,7 +475,7 @@
   protected void onFooterButtonInflated(Button button, @ColorInt int defaultButtonBackgroundColor) {
     // Try to set default background
     if (defaultButtonBackgroundColor != 0) {
-      updateButtonBackground(button, defaultButtonBackgroundColor);
+      FooterButtonStyleUtils.updateButtonBackground(button, defaultButtonBackgroundColor);
     } else {
       // TODO: get button background color from activity theme
     }
@@ -544,187 +608,30 @@
     if (!applyPartnerResources) {
       return;
     }
-    updateButtonTextColorWithPartnerConfig(
-        button, footerButtonPartnerConfig.getButtonTextColorConfig());
-    updateButtonTextSizeWithPartnerConfig(
-        button, footerButtonPartnerConfig.getButtonTextSizeConfig());
-    updateButtonTypeFaceWithPartnerConfig(
-        button, footerButtonPartnerConfig.getButtonTextTypeFaceConfig());
-    updateButtonBackgroundWithPartnerConfig(
+    FooterButtonStyleUtils.applyButtonPartnerResources(
+        context,
         button,
-        footerButtonPartnerConfig.getButtonBackgroundConfig(),
-        footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
-        footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
-    updateButtonRadiusWithPartnerConfig(button, footerButtonPartnerConfig.getButtonRadiusConfig());
-    updateButtonIconWithPartnerConfig(button, footerButtonPartnerConfig.getButtonIconConfig());
-    updateButtonRippleColorWithPartnerConfig(button, footerButtonPartnerConfig);
+        applyDynamicColor,
+        /* isButtonIconAtEnd= */ (button.getId() == primaryButtonId),
+        footerButtonPartnerConfig);
+    if (!applyDynamicColor) {
+      // adjust text color based on enabled state
+      updateButtonTextColorWithEnabledState(
+          button, footerButtonPartnerConfig.getButtonTextColorConfig());
+    }
   }
 
-  private void updateButtonTextColorWithPartnerConfig(
+  private void updateButtonTextColorWithEnabledState(
       Button button, PartnerConfig buttonTextColorConfig) {
     if (button.isEnabled()) {
-      @ColorInt
-      int color = PartnerConfigHelper.get(context).getColor(context, buttonTextColorConfig);
-      if (color != Color.TRANSPARENT) {
-        button.setTextColor(ColorStateList.valueOf(color));
-      }
+      FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
+          context, button, buttonTextColorConfig);
     } else {
-      // disable state will use the default disable state color
-      button.setTextColor(
-          button.getId() == primaryButtonId ? primaryDefaultTextColor : secondaryDefaultTextColor);
-    }
-  }
-
-  private void updateButtonTextSizeWithPartnerConfig(
-      Button button, PartnerConfig buttonTextSizeConfig) {
-    float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig);
-    if (size > 0) {
-      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
-    }
-  }
-
-  private void updateButtonTypeFaceWithPartnerConfig(
-      Button button, PartnerConfig buttonTextTypeFaceConfig) {
-    String fontFamilyName =
-        PartnerConfigHelper.get(context).getString(context, buttonTextTypeFaceConfig);
-    Typeface font = Typeface.create(fontFamilyName, Typeface.NORMAL);
-    if (font != null) {
-      button.setTypeface(font);
-    }
-  }
-
-  @TargetApi(VERSION_CODES.Q)
-  private void updateButtonBackgroundWithPartnerConfig(
-      Button button,
-      PartnerConfig buttonBackgroundConfig,
-      PartnerConfig buttonDisableAlphaConfig,
-      PartnerConfig buttonDisableBackgroundConfig) {
-    Preconditions.checkArgument(
-        Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q,
-        "Update button background only support on sdk Q or higher");
-    @ColorInt int color;
-    @ColorInt int disabledColor;
-    float disabledAlpha;
-    int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
-    int[] ENABLED_STATE_SET = {};
-    color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundConfig);
-    disabledAlpha =
-        PartnerConfigHelper.get(context).getFraction(context, buttonDisableAlphaConfig, 0f);
-    disabledColor =
-        PartnerConfigHelper.get(context).getColor(context, buttonDisableBackgroundConfig);
-
-    if (color != Color.TRANSPARENT) {
-      if (disabledAlpha <= 0f) {
-        // if no partner resource, fallback to theme disable alpha
-        float alpha;
-        TypedArray a = context.obtainStyledAttributes(new int[] {android.R.attr.disabledAlpha});
-        alpha = a.getFloat(0, DEFAULT_DISABLED_ALPHA);
-        a.recycle();
-        disabledAlpha = alpha;
-      }
-      if (disabledColor == Color.TRANSPARENT) {
-        // if no partner resource, fallback to button background color
-        disabledColor = color;
-      }
-
-      // Set text color for ripple.
-      ColorStateList colorStateList =
-          new ColorStateList(
-              new int[][] {DISABLED_STATE_SET, ENABLED_STATE_SET},
-              new int[] {convertRgbToArgb(disabledColor, disabledAlpha), color});
-
-      // b/129482013: When a LayerDrawable is mutated, a new clone of its children drawables are
-      // created, but without copying the state from the parent drawable. So even though the
-      // parent is getting the correct drawable state from the view, the children won't get those
-      // states until a state change happens.
-      // As a workaround, we mutate the drawable and forcibly set the state to empty, and then
-      // refresh the state so the children will have the updated states.
-      button.getBackground().mutate().setState(new int[0]);
-      button.refreshDrawableState();
-      button.setBackgroundTintList(colorStateList);
-    }
-  }
-
-  private void updateButtonBackground(Button button, @ColorInt int color) {
-    button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP);
-  }
-
-  private void updateButtonRadiusWithPartnerConfig(
-      Button button, PartnerConfig buttonRadiusConfig) {
-    if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
-      float radius = PartnerConfigHelper.get(context).getDimension(context, buttonRadiusConfig);
-      GradientDrawable gradientDrawable = getGradientDrawable(button);
-      if (gradientDrawable != null) {
-        gradientDrawable.setCornerRadius(radius);
-      }
-    }
-  }
-
-  private void updateButtonRippleColorWithPartnerConfig(
-      Button button, FooterButtonPartnerConfig footerButtonPartnerConfig) {
-    // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
-    // unavailable. Since Stencil customization provider only works on Q+, there is no need to
-    // perform any customization for versions 21.
-    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
-      RippleDrawable rippleDrawable = getRippleDrawable(button);
-      if (rippleDrawable == null) {
-        return;
-      }
-
-      int[] pressedState = {android.R.attr.state_pressed};
-      @ColorInt int color;
-      // Get partner text color.
-      color =
-          PartnerConfigHelper.get(context)
-              .getColor(context, footerButtonPartnerConfig.getButtonTextColorConfig());
-
-      float alpha =
-          PartnerConfigHelper.get(context)
-              .getFraction(context, footerButtonPartnerConfig.getButtonRippleColorAlphaConfig());
-
-      // Set text color for ripple.
-      ColorStateList colorStateList =
-          new ColorStateList(
-              new int[][] {pressedState, StateSet.NOTHING},
-              new int[] {convertRgbToArgb(color, alpha), Color.TRANSPARENT});
-      rippleDrawable.setColor(colorStateList);
-    }
-  }
-
-  private void updateButtonIconWithPartnerConfig(Button button, PartnerConfig buttonIconConfig) {
-    if (button == null) {
-      return;
-    }
-    Drawable icon = null;
-    if (buttonIconConfig != null) {
-      icon = PartnerConfigHelper.get(context).getDrawable(context, buttonIconConfig);
-    }
-    setButtonIcon(button, icon);
-  }
-
-  private void setButtonIcon(Button button, Drawable icon) {
-    if (button == null) {
-      return;
-    }
-
-    if (icon != null) {
-      // TODO: restrict the icons to a reasonable size
-      int h = icon.getIntrinsicHeight();
-      int w = icon.getIntrinsicWidth();
-      icon.setBounds(0, 0, w, h);
-    }
-
-    Drawable iconStart = null;
-    Drawable iconEnd = null;
-    if (button.getId() == primaryButtonId) {
-      iconEnd = icon;
-    } else if (button.getId() == secondaryButtonId) {
-      iconStart = icon;
-    }
-    if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
-      button.setCompoundDrawablesRelative(iconStart, null, iconEnd, null);
-    } else {
-      button.setCompoundDrawables(iconStart, null, iconEnd, null);
+      FooterButtonStyleUtils.updateButtonTextDisableColor(
+          button,
+          /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle)
+              ? primaryDefaultTextColor
+              : secondaryDefaultTextColor);
     }
   }
 
@@ -763,43 +670,6 @@
     return result;
   }
 
-  GradientDrawable getGradientDrawable(Button button) {
-    // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
-    // sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only
-    // works on Q+, there is no need to perform any customization for versions 21.
-    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
-      Drawable drawable = button.getBackground();
-      if (drawable instanceof InsetDrawable) {
-        LayerDrawable layerDrawable = (LayerDrawable) ((InsetDrawable) drawable).getDrawable();
-        return (GradientDrawable) layerDrawable.getDrawable(0);
-      } else if (drawable instanceof RippleDrawable) {
-        InsetDrawable insetDrawable = (InsetDrawable) ((RippleDrawable) drawable).getDrawable(0);
-        return (GradientDrawable) insetDrawable.getDrawable();
-      }
-    }
-    return null;
-  }
-
-  RippleDrawable getRippleDrawable(Button button) {
-    // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
-    // unavailable. Since Stencil customization provider only works on Q+, there is no need to
-    // perform any customization for versions 21.
-    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
-      Drawable drawable = button.getBackground();
-      if (drawable instanceof InsetDrawable) {
-        return (RippleDrawable) ((InsetDrawable) drawable).getDrawable();
-      } else if (drawable instanceof RippleDrawable) {
-        return (RippleDrawable) drawable;
-      }
-    }
-    return null;
-  }
-
-  @ColorInt
-  private static int convertRgbToArgb(@ColorInt int color, float alpha) {
-    return Color.argb((int) (alpha * 255), Color.red(color), Color.green(color), Color.blue(color));
-  }
-
   protected View inflateFooter(@LayoutRes int footer) {
     if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
       LayoutInflater inflater =
diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java
index 2fa8c7c..90c13ec 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButton.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButton.java
@@ -23,17 +23,18 @@
 import android.content.res.TypedArray;
 import android.os.Build.VERSION_CODES;
 import android.os.PersistableBundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.View.OnClickListener;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.annotation.StyleRes;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.View.OnClickListener;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.logging.CustomEvent;
 import java.lang.annotation.Retention;
+import java.util.Locale;
 
 /**
  * Definition of a footer button. Clients can use this class to customize attributes like text,
@@ -53,6 +54,8 @@
   private OnClickListener onClickListenerWhenDisabled;
   private OnButtonEventListener buttonListener;
   private int clickCount = 0;
+  private Locale locale;
+  private int direction;
 
   public FooterButton(Context context, AttributeSet attrs) {
     TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SucFooterButton);
@@ -78,11 +81,15 @@
       CharSequence text,
       @Nullable OnClickListener listener,
       @ButtonType int buttonType,
-      @StyleRes int theme) {
+      @StyleRes int theme,
+      Locale locale,
+      int direction) {
     this.text = text;
     onClickListener = listener;
     this.buttonType = buttonType;
     this.theme = theme;
+    this.locale = locale;
+    this.direction = direction;
   }
 
   /** Returns the text that this footer button is displaying. */
@@ -142,6 +149,16 @@
     return enabled;
   }
 
+  /** Returns the layout direction for this footer button. */
+  public int getLayoutDirection() {
+    return direction;
+  }
+
+  /** Returns the text locale for this footer button. */
+  public Locale getTextLocale() {
+    return locale;
+  }
+
   /**
    * Sets the visibility state of this footer button.
    *
@@ -172,6 +189,22 @@
     }
   }
 
+  /** Sets the text locale to be displayed on footer button. */
+  public void setTextLocale(Locale locale) {
+    this.locale = locale;
+    if (buttonListener != null) {
+      buttonListener.onLocaleChanged(locale);
+    }
+  }
+
+  /** Sets the layout direction to be displayed on footer button. */
+  public void setLayoutDirection(int direction) {
+    this.direction = direction;
+    if (buttonListener != null) {
+      buttonListener.onDirectionChanged(direction);
+    }
+  }
+
   /**
    * Registers a callback to be invoked when footer button API has set.
    *
@@ -201,6 +234,10 @@
     void onVisibilityChanged(int visibility);
 
     void onTextChanged(CharSequence text);
+
+    void onLocaleChanged(Locale locale);
+
+    void onDirectionChanged(int direction);
   }
 
   /** Maximum valid value of ButtonType */
@@ -308,12 +345,16 @@
    *         .setListener(primaryButton)
    *         .setButtonType(ButtonType.NEXT)
    *         .setTheme(R.style.SuwGlifButton_Primary)
+   *         .setTextLocale(Locale.CANADA)
+   *         .setLayoutDirection(View.LAYOUT_DIRECTION_LTR)
    *         .build();
    * </pre>
    */
   public static class Builder {
     private final Context context;
     private String text = "";
+    private Locale locale = null;
+    private int direction = -1;
     private OnClickListener onClickListener = null;
     @ButtonType private int buttonType = ButtonType.OTHER;
     private int theme = 0;
@@ -334,6 +375,18 @@
       return this;
     }
 
+    /** Sets the {@code locale} of FooterButton. */
+    public Builder setTextLocale(Locale locale) {
+      this.locale = locale;
+      return this;
+    }
+
+    /** Sets the {@code direction} of FooterButton. */
+    public Builder setLayoutDirection(int direction) {
+      this.direction = direction;
+      return this;
+    }
+
     /** Sets the {@code listener} of FooterButton. */
     public Builder setListener(@Nullable OnClickListener listener) {
       onClickListener = listener;
@@ -353,7 +406,7 @@
     }
 
     public FooterButton build() {
-      return new FooterButton(text, onClickListener, buttonType, theme);
+      return new FooterButton(text, onClickListener, buttonType, theme, locale, direction);
     }
   }
 }
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java b/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java
index fe2538b..10aa052 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonInflater.java
@@ -19,10 +19,10 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
-import androidx.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.Xml;
 import android.view.InflateException;
+import androidx.annotation.NonNull;
 import java.io.IOException;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
new file mode 100644
index 0000000..ef45b5c
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.template;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.RippleDrawable;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.util.StateSet;
+import android.util.TypedValue;
+import android.widget.Button;
+import androidx.annotation.ColorInt;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.setupcompat.R;
+import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
+import com.google.android.setupcompat.internal.Preconditions;
+import com.google.android.setupcompat.partnerconfig.PartnerConfig;
+import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+
+/** Utils for updating the button style. */
+public class FooterButtonStyleUtils {
+  private static final float DEFAULT_DISABLED_ALPHA = 0.26f;
+
+  /** Apply the partner primary button style to given {@code button}. */
+  public static void applyPrimaryButtonPartnerResource(
+      Context context, Button button, boolean applyDynamicColor) {
+
+    FooterButtonPartnerConfig footerButtonPartnerConfig =
+        new FooterButtonPartnerConfig.Builder(null)
+            .setPartnerTheme(R.style.SucPartnerCustomizationButton_Primary)
+            .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
+            .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
+            .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+            .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
+            .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
+            .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR)
+            .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+            .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
+            .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+            .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
+            .build();
+    applyButtonPartnerResources(
+        context,
+        button,
+        applyDynamicColor,
+        /* isButtonIconAtEnd= */ true,
+        footerButtonPartnerConfig);
+  }
+
+  /** Apply the partner secondary button style to given {@code button}. */
+  public static void applySecondaryButtonPartnerResource(
+      Context context, Button button, boolean applyDynamicColor) {
+
+    int defaultTheme = R.style.SucPartnerCustomizationButton_Secondary;
+    int color =
+        PartnerConfigHelper.get(context)
+            .getColor(context, PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR);
+    if (color != Color.TRANSPARENT) {
+      defaultTheme = R.style.SucPartnerCustomizationButton_Primary;
+    }
+    // Setup button partner config
+    FooterButtonPartnerConfig footerButtonPartnerConfig =
+        new FooterButtonPartnerConfig.Builder(null)
+            .setPartnerTheme(defaultTheme)
+            .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
+            .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
+            .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+            .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
+            .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
+            .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
+            .setTextSizeConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_SIZE)
+            .setButtonMinHeight(PartnerConfig.CONFIG_FOOTER_BUTTON_MIN_HEIGHT)
+            .setTextTypeFaceConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_FONT_FAMILY)
+            .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
+            .build();
+    applyButtonPartnerResources(
+        context,
+        button,
+        applyDynamicColor,
+        /* isButtonIconAtEnd= */ false,
+        footerButtonPartnerConfig);
+  }
+
+  static void applyButtonPartnerResources(
+      Context context,
+      Button button,
+      boolean applyDynamicColor,
+      boolean isButtonIconAtEnd,
+      FooterButtonPartnerConfig footerButtonPartnerConfig) {
+
+    // If dynamic color enabled, these colors won't be overrode by partner config.
+    // Instead, these colors align with the current theme colors.
+    if (!applyDynamicColor) {
+      // use default disable color util we support the partner disable text color
+      if (button.isEnabled()) {
+        FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
+            context, button, footerButtonPartnerConfig.getButtonTextColorConfig());
+      }
+      FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
+          context,
+          button,
+          footerButtonPartnerConfig.getButtonBackgroundConfig(),
+          footerButtonPartnerConfig.getButtonDisableAlphaConfig(),
+          footerButtonPartnerConfig.getButtonDisableBackgroundConfig());
+    }
+    FooterButtonStyleUtils.updateButtonRippleColorWithPartnerConfig(
+        context,
+        button,
+        applyDynamicColor,
+        footerButtonPartnerConfig.getButtonTextColorConfig(),
+        footerButtonPartnerConfig.getButtonRippleColorAlphaConfig());
+    FooterButtonStyleUtils.updateButtonTextSizeWithPartnerConfig(
+        context, button, footerButtonPartnerConfig.getButtonTextSizeConfig());
+    FooterButtonStyleUtils.updateButtonMinHeightWithPartnerConfig(
+        context, button, footerButtonPartnerConfig.getButtonMinHeightConfig());
+    FooterButtonStyleUtils.updateButtonTypeFaceWithPartnerConfig(
+        context,
+        button,
+        footerButtonPartnerConfig.getButtonTextTypeFaceConfig(),
+        footerButtonPartnerConfig.getButtonTextStyleConfig());
+    FooterButtonStyleUtils.updateButtonRadiusWithPartnerConfig(
+        context, button, footerButtonPartnerConfig.getButtonRadiusConfig());
+    FooterButtonStyleUtils.updateButtonIconWithPartnerConfig(
+        context, button, footerButtonPartnerConfig.getButtonIconConfig(), isButtonIconAtEnd);
+  }
+
+  static void updateButtonTextEnabledColorWithPartnerConfig(
+      Context context, Button button, PartnerConfig buttonEnableTextColorConfig) {
+    @ColorInt
+    int color = PartnerConfigHelper.get(context).getColor(context, buttonEnableTextColorConfig);
+    updateButtonTextEnabledColor(button, color);
+  }
+
+  static void updateButtonTextEnabledColor(Button button, @ColorInt int textColor) {
+    if (textColor != Color.TRANSPARENT) {
+      button.setTextColor(ColorStateList.valueOf(textColor));
+    }
+  }
+
+  static void updateButtonTextDisableColor(Button button, ColorStateList disabledTextColor) {
+    // TODO : add disable footer button text color partner config
+
+    // disable state will use the default disable state color
+    button.setTextColor(disabledTextColor);
+  }
+
+  @TargetApi(VERSION_CODES.Q)
+  static void updateButtonBackgroundWithPartnerConfig(
+      Context context,
+      Button button,
+      PartnerConfig buttonBackgroundConfig,
+      PartnerConfig buttonDisableAlphaConfig,
+      PartnerConfig buttonDisableBackgroundConfig) {
+    Preconditions.checkArgument(
+        Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q,
+        "Update button background only support on sdk Q or higher");
+    @ColorInt
+    int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundConfig);
+    float disabledAlpha =
+        PartnerConfigHelper.get(context).getFraction(context, buttonDisableAlphaConfig, 0f);
+    @ColorInt
+    int disabledColor =
+        PartnerConfigHelper.get(context).getColor(context, buttonDisableBackgroundConfig);
+
+    updateButtonBackgroundTintList(context, button, color, disabledAlpha, disabledColor);
+  }
+
+  @TargetApi(VERSION_CODES.Q)
+  static void updateButtonBackgroundTintList(
+      Context context,
+      Button button,
+      @ColorInt int color,
+      float disabledAlpha,
+      @ColorInt int disabledColor) {
+    int[] DISABLED_STATE_SET = {-android.R.attr.state_enabled};
+    int[] ENABLED_STATE_SET = {};
+
+    if (color != Color.TRANSPARENT) {
+      if (disabledAlpha <= 0f) {
+        // if no partner resource, fallback to theme disable alpha
+        TypedArray a = context.obtainStyledAttributes(new int[] {android.R.attr.disabledAlpha});
+        float alpha = a.getFloat(0, DEFAULT_DISABLED_ALPHA);
+        a.recycle();
+        disabledAlpha = alpha;
+      }
+      if (disabledColor == Color.TRANSPARENT) {
+        // if no partner resource, fallback to button background color
+        disabledColor = color;
+      }
+
+      // Set text color for ripple.
+      ColorStateList colorStateList =
+          new ColorStateList(
+              new int[][] {DISABLED_STATE_SET, ENABLED_STATE_SET},
+              new int[] {convertRgbToArgb(disabledColor, disabledAlpha), color});
+
+      // b/129482013: When a LayerDrawable is mutated, a new clone of its children drawables are
+      // created, but without copying the state from the parent drawable. So even though the
+      // parent is getting the correct drawable state from the view, the children won't get those
+      // states until a state change happens.
+      // As a workaround, we mutate the drawable and forcibly set the state to empty, and then
+      // refresh the state so the children will have the updated states.
+      button.getBackground().mutate().setState(new int[0]);
+      button.refreshDrawableState();
+      button.setBackgroundTintList(colorStateList);
+    }
+  }
+
+  @TargetApi(VERSION_CODES.Q)
+  static void updateButtonRippleColorWithPartnerConfig(
+      Context context,
+      Button button,
+      boolean applyDynamicColor,
+      PartnerConfig buttonTextColorConfig,
+      PartnerConfig buttonRippleColorAlphaConfig) {
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+
+      @ColorInt int textDefaultColor;
+      if (applyDynamicColor) {
+        // Get dynamic text color
+        textDefaultColor = button.getTextColors().getDefaultColor();
+      } else {
+        // Get partner text color.
+        textDefaultColor =
+            PartnerConfigHelper.get(context).getColor(context, buttonTextColorConfig);
+      }
+      float alpha =
+          PartnerConfigHelper.get(context).getFraction(context, buttonRippleColorAlphaConfig);
+      updateButtonRippleColor(button, textDefaultColor, alpha);
+    }
+  }
+
+  private static void updateButtonRippleColor(
+      Button button, @ColorInt int textColor, float rippleAlpha) {
+    // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
+    // unavailable. Since Stencil customization provider only works on Q+, there is no need to
+    // perform any customization for versions 21.
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+      RippleDrawable rippleDrawable = getRippleDrawable(button);
+      if (rippleDrawable == null) {
+        return;
+      }
+
+      int[] pressedState = {android.R.attr.state_pressed};
+
+      // Set text color for ripple.
+      ColorStateList colorStateList =
+          new ColorStateList(
+              new int[][] {pressedState, StateSet.NOTHING},
+              new int[] {convertRgbToArgb(textColor, rippleAlpha), Color.TRANSPARENT});
+      rippleDrawable.setColor(colorStateList);
+    }
+  }
+
+  static void updateButtonTextSizeWithPartnerConfig(
+      Context context, Button button, PartnerConfig buttonTextSizeConfig) {
+    float size = PartnerConfigHelper.get(context).getDimension(context, buttonTextSizeConfig);
+    if (size > 0) {
+      button.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
+    }
+  }
+
+  static void updateButtonMinHeightWithPartnerConfig(
+      Context context, Button button, PartnerConfig buttonMinHeightConfig) {
+    if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonMinHeightConfig)) {
+      float size = PartnerConfigHelper.get(context).getDimension(context, buttonMinHeightConfig);
+      if (size > 0) {
+        button.setMinHeight((int) size);
+      }
+    }
+  }
+
+  static void updateButtonTypeFaceWithPartnerConfig(
+      Context context,
+      Button button,
+      PartnerConfig buttonTextTypeFaceConfig,
+      PartnerConfig buttonTextStyleConfig) {
+    String fontFamilyName =
+        PartnerConfigHelper.get(context).getString(context, buttonTextTypeFaceConfig);
+
+    int textStyleValue = Typeface.NORMAL;
+    if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonTextStyleConfig)) {
+      textStyleValue =
+          PartnerConfigHelper.get(context)
+              .getInteger(context, buttonTextStyleConfig, Typeface.NORMAL);
+    }
+    Typeface font = Typeface.create(fontFamilyName, textStyleValue);
+    if (font != null) {
+      button.setTypeface(font);
+    }
+  }
+
+  static void updateButtonRadiusWithPartnerConfig(
+      Context context, Button button, PartnerConfig buttonRadiusConfig) {
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
+      float radius = PartnerConfigHelper.get(context).getDimension(context, buttonRadiusConfig);
+      GradientDrawable gradientDrawable = getGradientDrawable(button);
+      if (gradientDrawable != null) {
+        gradientDrawable.setCornerRadius(radius);
+      }
+    }
+  }
+
+  static void updateButtonIconWithPartnerConfig(
+      Context context, Button button, PartnerConfig buttonIconConfig, boolean isButtonIconAtEnd) {
+    if (button == null) {
+      return;
+    }
+    Drawable icon = null;
+    if (buttonIconConfig != null) {
+      icon = PartnerConfigHelper.get(context).getDrawable(context, buttonIconConfig);
+    }
+    setButtonIcon(button, icon, isButtonIconAtEnd);
+  }
+
+  private static void setButtonIcon(Button button, Drawable icon, boolean isButtonIconAtEnd) {
+    if (button == null) {
+      return;
+    }
+
+    if (icon != null) {
+      // TODO: restrict the icons to a reasonable size
+      int h = icon.getIntrinsicHeight();
+      int w = icon.getIntrinsicWidth();
+      icon.setBounds(0, 0, w, h);
+    }
+
+    Drawable iconStart = null;
+    Drawable iconEnd = null;
+    if (isButtonIconAtEnd) {
+      iconEnd = icon;
+    } else {
+      iconStart = icon;
+    }
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
+      button.setCompoundDrawablesRelative(iconStart, null, iconEnd, null);
+    } else {
+      button.setCompoundDrawables(iconStart, null, iconEnd, null);
+    }
+  }
+
+  static void updateButtonBackground(Button button, @ColorInt int color) {
+    button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP);
+  }
+
+  @VisibleForTesting
+  public static GradientDrawable getGradientDrawable(Button button) {
+    // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
+    // sdk 19. So check the sdk is higher than sdk 21 and since Stencil customization provider only
+    // works on Q+, there is no need to perform any customization for versions 21.
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+      Drawable drawable = button.getBackground();
+      if (drawable instanceof InsetDrawable) {
+        LayerDrawable layerDrawable = (LayerDrawable) ((InsetDrawable) drawable).getDrawable();
+        return (GradientDrawable) layerDrawable.getDrawable(0);
+      } else if (drawable instanceof RippleDrawable) {
+        if (((RippleDrawable) drawable).getDrawable(0) instanceof GradientDrawable) {
+          return (GradientDrawable) ((RippleDrawable) drawable).getDrawable(0);
+        }
+        InsetDrawable insetDrawable = (InsetDrawable) ((RippleDrawable) drawable).getDrawable(0);
+        return (GradientDrawable) insetDrawable.getDrawable();
+      }
+    }
+    return null;
+  }
+
+  static RippleDrawable getRippleDrawable(Button button) {
+    // RippleDrawable is available after sdk 21. And because on lower sdk the RippleDrawable is
+    // unavailable. Since Stencil customization provider only works on Q+, there is no need to
+    // perform any customization for versions 21.
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
+      Drawable drawable = button.getBackground();
+      if (drawable instanceof InsetDrawable) {
+        return (RippleDrawable) ((InsetDrawable) drawable).getDrawable();
+      } else if (drawable instanceof RippleDrawable) {
+        return (RippleDrawable) drawable;
+      }
+    }
+    return null;
+  }
+
+  @ColorInt
+  private static int convertRgbToArgb(@ColorInt int color, float alpha) {
+    return Color.argb((int) (alpha * 255), Color.red(color), Color.green(color), Color.blue(color));
+  }
+
+  private FooterButtonStyleUtils() {}
+}
diff --git a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
index 1bd6949..c0f1c45 100644
--- a/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/StatusBarMixin.java
@@ -25,13 +25,13 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
-import androidx.annotation.AttrRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.Window;
 import android.widget.LinearLayout;
+import androidx.annotation.AttrRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import com.google.android.setupcompat.PartnerCustomizationLayout;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
@@ -112,10 +112,14 @@
    */
   public void setStatusBarBackground(Drawable background) {
     if (partnerCustomizationLayout.shouldApplyPartnerResource()) {
+      // If full dynamic color enabled which means this activity is running outside of setup
+      // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3.
+      if (!partnerCustomizationLayout.useFullDynamicColor()) {
       Context context = partnerCustomizationLayout.getContext();
       background =
           PartnerConfigHelper.get(context)
               .getDrawable(context, PartnerConfig.CONFIG_STATUS_BAR_BACKGROUND);
+      }
     }
 
     if (statusBarLayout == null) {
diff --git a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
index e055d28..32c708c 100644
--- a/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/SystemNavBarMixin.java
@@ -24,13 +24,13 @@
 import android.os.Build;
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.Window;
 import androidx.annotation.AttrRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.Window;
 import com.google.android.setupcompat.PartnerCustomizationLayout;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.internal.TemplateLayout;
@@ -47,6 +47,7 @@
   private final TemplateLayout templateLayout;
   @Nullable private final Window windowOfActivity;
   @VisibleForTesting final boolean applyPartnerResources;
+  @VisibleForTesting final boolean useFullDynamicColor;
   private int sucSystemNavBarBackgroundColor = 0;
 
   /**
@@ -61,6 +62,10 @@
     this.applyPartnerResources =
         layout instanceof PartnerCustomizationLayout
             && ((PartnerCustomizationLayout) layout).shouldApplyPartnerResource();
+
+    this.useFullDynamicColor =
+        layout instanceof PartnerCustomizationLayout
+            && ((PartnerCustomizationLayout) layout).useFullDynamicColor();
   }
 
   /**
@@ -83,6 +88,19 @@
       setLightSystemNavBar(
           a.getBoolean(
               R.styleable.SucSystemNavBarMixin_sucLightSystemNavBar, isLightSystemNavBar()));
+
+      // Support updating system navigation bar divider color from P.
+      if (VERSION.SDK_INT >= VERSION_CODES.P) {
+        // get fallback value from theme
+        int[] navBarDividerColorAttr = new int[] {android.R.attr.navigationBarDividerColor};
+        TypedArray typedArray =
+            templateLayout.getContext().obtainStyledAttributes(navBarDividerColorAttr);
+        int defaultColor = typedArray.getColor(/* index= */ 0, /* defValue= */ 0);
+        int sucSystemNavBarDividerColor =
+            a.getColor(R.styleable.SucSystemNavBarMixin_sucSystemNavBarDividerColor, defaultColor);
+        setSystemNavBarDividerColor(sucSystemNavBarDividerColor);
+        typedArray.recycle();
+      }
       a.recycle();
     }
   }
@@ -96,10 +114,14 @@
   public void setSystemNavBarBackground(int color) {
     if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP && windowOfActivity != null) {
       if (applyPartnerResources) {
-        Context context = templateLayout.getContext();
-        color =
-            PartnerConfigHelper.get(context)
-                .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR);
+        // If full dynamic color enabled which means this activity is running outside of setup
+        // flow, the colors should refer to R.style.SudFullDynamicColorThemeGlifV3.
+        if (!useFullDynamicColor) {
+          Context context = templateLayout.getContext();
+          color =
+              PartnerConfigHelper.get(context)
+                  .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_BG_COLOR);
+        }
       }
       windowOfActivity.setNavigationBarColor(color);
     }
@@ -120,6 +142,7 @@
    *
    * @param isLight true means compatible with light theme, otherwise compatible with dark theme
    */
+
   public void setLightSystemNavBar(boolean isLight) {
     if (Build.VERSION.SDK_INT >= VERSION_CODES.O && windowOfActivity != null) {
       if (applyPartnerResources) {
@@ -158,6 +181,28 @@
   }
 
   /**
+   * Sets the divider color of navigation bar. The color will be overridden by partner resource if
+   * the activity is running in setup wizard flow.
+   *
+   * @param color the default divider color of navigation bar
+   */
+  public void setSystemNavBarDividerColor(int color) {
+    if (Build.VERSION.SDK_INT >= VERSION_CODES.P && windowOfActivity != null) {
+      if (applyPartnerResources) {
+        Context context = templateLayout.getContext();
+        // Do nothing if the old version partner provider did not contain the new config.
+        if (PartnerConfigHelper.get(context)
+            .isPartnerConfigAvailable(PartnerConfig.CONFIG_NAVIGATION_BAR_DIVIDER_COLOR)) {
+          color =
+              PartnerConfigHelper.get(context)
+                  .getColor(context, PartnerConfig.CONFIG_NAVIGATION_BAR_DIVIDER_COLOR);
+        }
+      }
+      windowOfActivity.setNavigationBarDividerColor(color);
+    }
+  }
+
+  /**
    * Hides the navigation bar, make the color of the status and navigation bars transparent, and
    * specify {@link View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} flag so that the content is laid-out
    * behind the transparent status bar. This is commonly used with {@link
diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
new file mode 100644
index 0000000..ea54745
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import android.os.Build;
+
+/**
+ * An util class to check whether the current OS version is higher or equal to sdk version of
+ * device.
+ */
+public final class BuildCompatUtils {
+
+  /**
+   * 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
+   * may take weeks for that to propagate to stable/prerelease/experimental SDKs in Google3. Also it
+   * can be different in all these channels. This can cause random issues, especially with sidecars
+   * (i.e., the code running on R may not know that it runs on R).
+   *
+   * <p>This still should try using BuildCompat.isAtLeastR() as source of truth, but also checking
+   * for VERSION_SDK_INT and VERSION.CODENAME in case when BuildCompat implementation returned
+   * false. Note that both checks should be >= and not = to make sure that when Android version
+   * increases (i.e., from R to S), this does not stop working.
+   *
+   * <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+)
+   * </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
+   * finalization.
+   *
+   * @return Whether the current OS version is higher or equal to S.
+   */
+  public static boolean isAtLeastS() {
+    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+      return false;
+    }
+    return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 31)
+        || (Build.VERSION.CODENAME.length() == 1
+            && Build.VERSION.CODENAME.charAt(0) >= 'S'
+            && Build.VERSION.CODENAME.charAt(0) <= 'Z');
+  }
+
+  private BuildCompatUtils() {}
+}
diff --git a/main/java/com/google/android/setupcompat/util/Logger.java b/main/java/com/google/android/setupcompat/util/Logger.java
new file mode 100644
index 0000000..3f8dfd1
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/util/Logger.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import android.util.Log;
+
+/**
+ * Helper class that wraps {@link Log} to log messages to logcat. This class consolidate the log
+ * {@link #TAG} in both SetupCompat and SetupDesign library.
+ *
+ * <p>When logging verbose and debug logs, the logs should either be guarded by {@code if
+ * (logger.isV())}, or a constant if (DEBUG). That DEBUG constant should be false on any submitted
+ * code.
+ */
+public final class Logger {
+
+  public static final String TAG = "SetupLibrary";
+
+  private final String prefix;
+
+  public Logger(Class<?> cls) {
+    this(cls.getSimpleName());
+  }
+
+  public Logger(String prefix) {
+    this.prefix = "[" + prefix + "] ";
+  }
+
+  public boolean isV() {
+    return Log.isLoggable(TAG, Log.VERBOSE);
+  }
+
+  public boolean isD() {
+    return Log.isLoggable(TAG, Log.DEBUG);
+  }
+
+  public boolean isI() {
+    return Log.isLoggable(TAG, Log.INFO);
+  }
+
+  public void atVerbose(String message) {
+    if (isV()) {
+      Log.v(TAG, prefix.concat(message));
+    }
+  }
+
+  public void atDebug(String message) {
+    if (isD()) {
+      Log.d(TAG, prefix.concat(message));
+    }
+  }
+
+  public void atInfo(String message) {
+    if (isI()) {
+      Log.i(TAG, prefix.concat(message));
+    }
+  }
+
+  public void w(String message) {
+    Log.w(TAG, prefix.concat(message));
+  }
+
+  public void e(String message) {
+    Log.e(TAG, prefix.concat(message));
+  }
+
+  public void e(String message, Throwable throwable) {
+    Log.e(TAG, prefix.concat(message), throwable);
+  }
+}
diff --git a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java
index 75e5dd3..dd92501 100644
--- a/main/java/com/google/android/setupcompat/util/SystemBarHelper.java
+++ b/main/java/com/google/android/setupcompat/util/SystemBarHelper.java
@@ -24,13 +24,12 @@
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
 import android.os.Handler;
-import androidx.annotation.RequiresPermission;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import androidx.annotation.RequiresPermission;
 
 /**
  * A helper class to manage the system navigation bar and status bar. This will add various
@@ -44,7 +43,7 @@
  */
 public final class SystemBarHelper {
 
-  private static final String TAG = "SystemBarHelper";
+  private static final Logger LOG = new Logger("SystemBarHelper");
 
   /** Needs to be equal to View.STATUS_BAR_DISABLE_BACK */
   private static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
@@ -329,7 +328,7 @@
                 // If the decor view is not installed yet, try again in the next loop.
                 handler.post(checkDecorViewRunnable);
               } else {
-                Log.w(TAG, "Cannot get decor view of window: " + window);
+                LOG.e("Cannot get decor view of window: " + window);
               }
             }
           }
diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
index bfe1dbb..79976bc 100644
--- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
+++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
@@ -34,7 +34,7 @@
  */
 public final class WizardManagerHelper {
 
-  private static final String ACTION_NEXT = "com.android.wizard.NEXT";
+  @VisibleForTesting public static final String ACTION_NEXT = "com.android.wizard.NEXT";
 
   // EXTRA_SCRIPT_URI and EXTRA_ACTION_ID are used in setup wizard in versions before M and are
   // kept for backwards compatibility.
@@ -43,10 +43,27 @@
 
   @VisibleForTesting static final String EXTRA_WIZARD_BUNDLE = "wizardBundle";
   private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
-  @VisibleForTesting public static final String EXTRA_IS_FIRST_RUN = "firstRun";
-  @VisibleForTesting static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
-  @VisibleForTesting static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
-  @VisibleForTesting public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow";
+
+  /** Extra for notifying an Activity that it is inside the first SetupWizard flow or not. */
+  public static final String EXTRA_IS_FIRST_RUN = "firstRun";
+
+  /** Extra for notifying an Activity that it is inside the Deferred SetupWizard flow or not. */
+  public static final String EXTRA_IS_DEFERRED_SETUP = "deferredSetup";
+
+  /** Extra for notifying an Activity that it is inside the "Pre-Deferred Setup" flow. */
+  public static final String EXTRA_IS_PRE_DEFERRED_SETUP = "preDeferredSetup";
+
+  /** Extra for notifying an Activity that it is inside the "Portal Setup" flow. */
+  public static final String EXTRA_IS_PORTAL_SETUP = "portalSetup";
+
+  /**
+   * Extra for notifying an Activity that it is inside the any setup flow.
+   *
+   * <p>Apps that target API levels below {@link android.os.Build.VERSION_CODES#Q} is able to
+   * determine whether Activity is inside the any setup flow by one of {@link #EXTRA_IS_FIRST_RUN},
+   * {@link #EXTRA_IS_DEFERRED_SETUP}, and {@link #EXTRA_IS_PRE_DEFERRED_SETUP} is true.
+   */
+  public static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow";
 
   public static final String EXTRA_THEME = "theme";
   public static final String EXTRA_USE_IMMERSIVE_MODE = "useImmersiveMode";
@@ -104,6 +121,7 @@
             EXTRA_IS_FIRST_RUN,
             EXTRA_IS_DEFERRED_SETUP,
             EXTRA_IS_PRE_DEFERRED_SETUP,
+            EXTRA_IS_PORTAL_SETUP,
             EXTRA_IS_SETUP_FLOW)) {
       dstIntent.putExtra(key, srcIntent.getBooleanExtra(key, false));
     }
diff --git a/main/res/values/attrs.xml b/main/res/values/attrs.xml
index 1a5342c..07f87ed 100644
--- a/main/res/values/attrs.xml
+++ b/main/res/values/attrs.xml
@@ -32,6 +32,7 @@
              This attribute will be ignored and use partner resource when inside setup wizard flow.
              The default value is true. -->
         <attr name="sucUsePartnerResource" format="boolean" />
+        <attr name="sucFullDynamicColor" format="boolean" />
     </declare-styleable>
 
     <!-- Status bar attributes; only takes effect on M or above -->
@@ -52,6 +53,10 @@
              "android:windowTranslucentNavigation" should be set to false. -->
         <attr name="sucSystemNavBarBackgroundColor" format="color" />
         <attr name="sucLightSystemNavBar" format="boolean" />
+        <!-- The color for the system navigation bar divider. For this to take effect,
+             "android:windowDrawsSystemBarBackgrounds" should be set to true and
+             "android:windowTranslucentNavigation" should be set to false. -->
+        <attr name="sucSystemNavBarDividerColor" format="color" />
     </declare-styleable>
 
     <!-- FooterButton attributes -->
@@ -70,6 +75,8 @@
             <enum name="skip" value="7" />
             <enum name="stop" value="8" />
         </attr>
+        <attr name="sucFooterButtonPaddingStart" format="dimension" />
+        <attr name="sucFooterButtonPaddingEnd" format="dimension" />
     </declare-styleable>
 
     <!-- Button of footer attributes -->
@@ -87,11 +94,18 @@
         <attr name="sucFooterBarButtonColorControlHighlight" format="color" />
         <attr name="sucFooterBarButtonColorControlHighlightRipple" format="color" />
         <attr name="sucFooterBarPaddingVertical" format="dimension" />
+        <attr name="sucFooterBarPaddingStart" format="dimension" />
+        <attr name="sucFooterBarPaddingEnd" format="dimension" />
+        <attr name="sucFooterBarMinHeight" format="dimension" />
     </declare-styleable>
 
     <declare-styleable name="SucHeaderMixin">
         <attr name="sucHeaderText" format="string" localization="suggested" />
         <attr name="sucHeaderTextColor" format="reference|color" />
+        <attr name="sucGlifHeaderMarginTop" format="dimension" />
+        <attr name="sucGlifHeaderMarginBottom" format="dimension" />
+        <attr name="sucGlifIconMarginTop" format="dimension" />
+        <attr name="sucHeaderContainerMarginBottom" format="dimension" />
     </declare-styleable>
 
 </resources>
diff --git a/main/res/values/dimens.xml b/main/res/values/dimens.xml
deleted file mode 100644
index ef3d98d..0000000
--- a/main/res/values/dimens.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    Copyright (C) 2018 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.
--->
-
-<resources>
-
-    <!-- TODO: Remove default values from setup compat, use from theme -->
-    <!-- Footer button bar style padding attributes-->
-    <dimen name="suc_customization_footer_min_height">72dp</dimen>
-    <dimen name="suc_customization_button_margin_start">8dp</dimen>
-    <dimen name="suc_customization_button_margin_end">20dp</dimen>
-
-    <!-- Footer button style padding attributes -->
-    <dimen name="suc_customization_button_padding">16dp</dimen>
-
-</resources>
diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml
index 48fcddf..6474426 100644
--- a/main/res/values/styles.xml
+++ b/main/res/values/styles.xml
@@ -27,14 +27,14 @@
         <item name="android:clipChildren">false</item>
         <item name="android:clipToPadding">false</item>
         <item name="android:gravity">center_vertical</item>
-        <item name="android:minHeight">@dimen/suc_customization_footer_min_height</item>
+        <item name="android:minHeight">?attr/sucFooterBarMinHeight</item>
         <item name="android:orientation">horizontal</item>
         <item name="android:paddingTop">?attr/sucFooterBarPaddingVertical</item>
         <item name="android:paddingBottom">?attr/sucFooterBarPaddingVertical</item>
-        <item name="android:paddingEnd" tools:ignore="NewApi">@dimen/suc_customization_button_margin_end</item>
-        <item name="android:paddingLeft">@dimen/suc_customization_button_margin_start</item>
-        <item name="android:paddingRight">@dimen/suc_customization_button_margin_end</item>
-        <item name="android:paddingStart" tools:ignore="NewApi">@dimen/suc_customization_button_margin_start</item>
+        <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterBarPaddingEnd</item>
+        <item name="android:paddingLeft">?attr/sucFooterBarPaddingStart</item>
+        <item name="android:paddingRight">?attr/sucFooterBarPaddingEnd</item>
+        <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterBarPaddingStart</item>
     </style>
 
     <style name="SucPartnerCustomizationButton.Primary" parent="android:Widget.Material.Button.Colored">
@@ -46,8 +46,10 @@
 
         <!-- Values used in styles -->
         <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
-        <item name="android:paddingLeft">@dimen/suc_customization_button_padding</item>
-        <item name="android:paddingRight">@dimen/suc_customization_button_padding</item>
+        <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
+        <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
+        <item name="android:paddingRight">?attr/sucFooterButtonPaddingEnd</item>
+        <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterButtonPaddingEnd</item>
         <item name="android:textAllCaps">?attr/sucFooterBarButtonAllCaps</item>
         <item name="android:stateListAnimator">@null</item>
 
@@ -65,8 +67,10 @@
         <!-- Values used in styles -->
         <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
         <item name="android:minWidth">0dp</item>
-        <item name="android:paddingLeft">@dimen/suc_customization_button_padding</item>
-        <item name="android:paddingRight">@dimen/suc_customization_button_padding</item>
+        <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
+        <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
+        <item name="android:paddingRight">?attr/sucFooterButtonPaddingEnd</item>
+        <item name="android:paddingEnd" tools:ignore="NewApi">?attr/sucFooterButtonPaddingEnd</item>
         <item name="android:textAllCaps">?attr/sucFooterBarButtonAllCaps</item>
 
         <!-- Values used in themes -->
diff --git a/partnerconfig/AndroidManifest.xml b/partnerconfig/AndroidManifest.xml
index c95a4dd..43e2041 100644
--- a/partnerconfig/AndroidManifest.xml
+++ b/partnerconfig/AndroidManifest.xml
@@ -16,9 +16,16 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     package="com.google.android.setupcompat.partnerconfig">
 
-  <uses-sdk
-      android:minSdkVersion="14"
-      android:targetSdkVersion="28" />
+  <uses-sdk />
+
+  <!-- after SDK 30, package need to declare its visible packages. -->
+  <queries tools:node="merge">
+    <intent>
+      <action android:name="com.android.setupwizard.action.PARTNER_CUSTOMIZATION" />
+    </intent>
+    <provider android:authorities="com.google.android.setupwizard.partner" />
+  </queries>
 </manifest>
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index 56256c9..280ab81 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -30,9 +30,16 @@
   // Navigation bar background color
   CONFIG_NAVIGATION_BAR_BG_COLOR(PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR, ResourceType.COLOR),
 
+  // Navigation bar divider color
+  CONFIG_NAVIGATION_BAR_DIVIDER_COLOR(
+      PartnerConfigKey.KEY_NAVIGATION_BAR_DIVIDER_COLOR, ResourceType.COLOR),
+
   // Background color of the footer bar.
   CONFIG_FOOTER_BAR_BG_COLOR(PartnerConfigKey.KEY_FOOTER_BAR_BG_COLOR, ResourceType.COLOR),
 
+  // The min height of the footer buttons
+  CONFIG_FOOTER_BAR_MIN_HEIGHT(PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT, 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),
@@ -89,10 +96,18 @@
   CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA(
       PartnerConfigKey.KEY_FOOTER_BUTTON_RIPPLE_ALPHA, ResourceType.FRACTION),
 
-  // Text size of the primary footer button
+  // Text size of the footer buttons
   CONFIG_FOOTER_BUTTON_TEXT_SIZE(
       PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE, ResourceType.DIMENSION),
 
+  // The text style of footer buttons {0 = NORMAL}, {1 = BOLD}, {2 = ITALIC}, {3 = BOLD_ITALIC}
+  CONFIG_FOOTER_BUTTON_TEXT_STYLE(
+      PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_STYLE, ResourceType.INTEGER),
+
+  // The min height of the footer buttons
+  CONFIG_FOOTER_BUTTON_MIN_HEIGHT(
+      PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT, ResourceType.DIMENSION),
+
   // Disabled background alpha of the footer buttons
   CONFIG_FOOTER_BUTTON_DISABLED_ALPHA(
       PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA, ResourceType.FRACTION),
@@ -120,6 +135,12 @@
   // Background color of layout
   CONFIG_LAYOUT_BACKGROUND_COLOR(PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR, ResourceType.COLOR),
 
+  // Margin start of the layout
+  CONFIG_LAYOUT_MARGIN_START(PartnerConfigKey.KEY_LAYOUT_MARGIN_START, ResourceType.DIMENSION),
+
+  // Margin end of the layout
+  CONFIG_LAYOUT_MARGIN_END(PartnerConfigKey.KEY_LAYOUT_MARGIN_END, ResourceType.DIMENSION),
+
   // Text color of the header
   CONFIG_HEADER_TEXT_COLOR(PartnerConfigKey.KEY_HEADER_TEXT_COLOR, ResourceType.COLOR),
 
@@ -129,13 +150,50 @@
   // Font family of the header
   CONFIG_HEADER_FONT_FAMILY(PartnerConfigKey.KEY_HEADER_FONT_FAMILY, ResourceType.STRING),
 
+  // Margin top of the header text
+  CONFIG_HEADER_TEXT_MARGIN_TOP(
+      PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_TOP, ResourceType.DIMENSION),
+
+  // Margin bottom of the header text
+  CONFIG_HEADER_TEXT_MARGIN_BOTTOM(
+      PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_BOTTOM, ResourceType.DIMENSION),
+
   // Gravity of the header, icon and description
   CONFIG_LAYOUT_GRAVITY(PartnerConfigKey.KEY_LAYOUT_GRAVITY, ResourceType.STRING),
 
+  // Margin top of the icon
+  CONFIG_ICON_MARGIN_TOP(PartnerConfigKey.KEY_ICON_MARGIN_TOP, ResourceType.DIMENSION),
+
+  // Size of the icon
+  CONFIG_ICON_SIZE(PartnerConfigKey.KEY_ICON_SIZE, ResourceType.DIMENSION),
+
   // Background color of the header area
   CONFIG_HEADER_AREA_BACKGROUND_COLOR(
       PartnerConfigKey.KEY_HEADER_AREA_BACKGROUND_COLOR, ResourceType.COLOR),
 
+  // Margin bottom of the header container
+  CONFIG_HEADER_CONTAINER_MARGIN_BOTTOM(
+      PartnerConfigKey.KEY_HEADER_CONTAINER_MARGIN_BOTTOM, ResourceType.DIMENSION),
+
+  // Auto text size enabled status
+  CONFIG_HEADER_AUTO_SIZE_ENABLED(PartnerConfigKey.KEY_HEADER_AUTO_SIZE_ENABLED, ResourceType.BOOL),
+
+  // Max text size of header when auto size enabled. Ignored if auto size is false.
+  CONFIG_HEADER_AUTO_SIZE_MAX_TEXT_SIZE(
+      PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_TEXT_SIZE, ResourceType.DIMENSION),
+
+  // Min text size of header when auto size enabled. Ignored if auto size is false.
+  CONFIG_HEADER_AUTO_SIZE_MIN_TEXT_SIZE(
+      PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MIN_TEXT_SIZE, ResourceType.DIMENSION),
+
+  // The max lines of the max text size when auto size enabled. Ignored if auto size is false.
+  CONFIG_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE(
+      PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE, ResourceType.INTEGER),
+
+  // Extra line spacing of header when auto size enabled. Ignored if auto size is false.
+  CONFIG_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA(
+      PartnerConfigKey.KEY_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA, ResourceType.DIMENSION),
+
   // Text size of the description
   CONFIG_DESCRIPTION_TEXT_SIZE(PartnerConfigKey.KEY_DESCRIPTION_TEXT_SIZE, ResourceType.DIMENSION),
 
@@ -149,6 +207,14 @@
   // Font family of the description
   CONFIG_DESCRIPTION_FONT_FAMILY(PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY, ResourceType.STRING),
 
+  // Margin top of the description text
+  CONFIG_DESCRIPTION_TEXT_MARGIN_TOP(
+      PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP, ResourceType.DIMENSION),
+
+  // Margin bottom of the description text
+  CONFIG_DESCRIPTION_TEXT_MARGIN_BOTTOM(
+      PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM, ResourceType.DIMENSION),
+
   // Text size of the body content text
   CONFIG_CONTENT_TEXT_SIZE(PartnerConfigKey.KEY_CONTENT_TEXT_SIZE, ResourceType.DIMENSION),
 
@@ -164,6 +230,75 @@
   // Gravity of the body content text
   CONFIG_CONTENT_LAYOUT_GRAVITY(PartnerConfigKey.KEY_CONTENT_LAYOUT_GRAVITY, ResourceType.STRING),
 
+  // The padding top of the content
+  CONFIG_CONTENT_PADDING_TOP(PartnerConfigKey.KEY_CONTENT_PADDING_TOP, ResourceType.DIMENSION),
+
+  // The text size of the content info.
+  CONFIG_CONTENT_INFO_TEXT_SIZE(
+      PartnerConfigKey.KEY_CONTENT_INFO_TEXT_SIZE, ResourceType.DIMENSION),
+
+  // The font family of the content info.
+  CONFIG_CONTENT_INFO_FONT_FAMILY(
+      PartnerConfigKey.KEY_CONTENT_INFO_FONT_FAMILY, ResourceType.STRING),
+
+  // The text line spacing extra of the content info.
+  CONFIG_CONTENT_INFO_LINE_SPACING_EXTRA(
+      PartnerConfigKey.KEY_CONTENT_INFO_LINE_SPACING_EXTRA, ResourceType.DIMENSION),
+
+  // The icon size of the content info.
+  CONFIG_CONTENT_INFO_ICON_SIZE(
+      PartnerConfigKey.KEY_CONTENT_INFO_ICON_SIZE, ResourceType.DIMENSION),
+
+  // The icon margin end of the content info.
+  CONFIG_CONTENT_INFO_ICON_MARGIN_END(
+      PartnerConfigKey.KEY_CONTENT_INFO_ICON_MARGIN_END, ResourceType.DIMENSION),
+
+  // The padding top of the content info.
+  CONFIG_CONTENT_INFO_PADDING_TOP(
+      PartnerConfigKey.KEY_CONTENT_INFO_PADDING_TOP, ResourceType.DIMENSION),
+
+  // The padding bottom of the content info.
+  CONFIG_CONTENT_INFO_PADDING_BOTTOM(
+      PartnerConfigKey.KEY_CONTENT_INFO_PADDING_BOTTOM, ResourceType.DIMENSION),
+
+  // The title text size of list items.
+  CONFIG_ITEMS_TITLE_TEXT_SIZE(PartnerConfigKey.KEY_ITEMS_TITLE_TEXT_SIZE, ResourceType.DIMENSION),
+
+  // The summary text size of list items.
+  CONFIG_ITEMS_SUMMARY_TEXT_SIZE(
+      PartnerConfigKey.KEY_ITEMS_SUMMARY_TEXT_SIZE, ResourceType.DIMENSION),
+
+  // The summary margin top of list items.
+  CONFIG_ITEMS_SUMMARY_MARGIN_TOP(
+      PartnerConfigKey.KEY_ITEMS_SUMMARY_MARGIN_TOP, ResourceType.DIMENSION),
+
+  // The title font family of list items.
+  CONFIG_ITEMS_TITLE_FONT_FAMILY(PartnerConfigKey.KEY_ITEMS_TITLE_FONT_FAMILY, ResourceType.STRING),
+
+  // The summary font family of list items.
+  CONFIG_ITEMS_SUMMARY_FONT_FAMILY(
+      PartnerConfigKey.KEY_ITEMS_SUMMARY_FONT_FAMILY, ResourceType.STRING),
+
+  // The padding top of list items.
+  CONFIG_ITEMS_PADDING_TOP(PartnerConfigKey.KEY_ITEMS_PADDING_TOP, ResourceType.DIMENSION),
+
+  // The padding bottom of list items.
+  CONFIG_ITEMS_PADDING_BOTTOM(PartnerConfigKey.KEY_ITEMS_PADDING_BOTTOM, ResourceType.DIMENSION),
+
+  // The minimum height of list items.
+  CONFIG_ITEMS_MIN_HEIGHT(PartnerConfigKey.KEY_ITEMS_MIN_HEIGHT, ResourceType.DIMENSION),
+
+  // 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.
+  CONFIG_CARD_VIEW_INTRINSIC_WIDTH(
+      PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH, ResourceType.DIMENSION),
+
+  // The intrinsic height of the card view for foldabe/tablet.
+  CONFIG_CARD_VIEW_INTRINSIC_HEIGHT(
+      PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT, ResourceType.DIMENSION),
+
   // The animation of loading screen used in those activities which is non of below type.
   CONFIG_PROGRESS_ILLUSTRATION_DEFAULT(
       PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DEFAULT, ResourceType.ILLUSTRATION),
@@ -184,9 +319,104 @@
   CONFIG_PROGRESS_ILLUSTRATION_UPDATE(
       PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_UPDATE, ResourceType.ILLUSTRATION),
 
-  CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS(
-      PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, ResourceType.INTEGER);
+  // The animation of loading screen used in those activities which is finishing setup.
+  // For example:com.google.android.setupwizard.FINAL_HOLD
+  CONFIG_PROGRESS_ILLUSTRATION_FINAL_HOLD(
+      PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_FINAL_HOLD, ResourceType.ILLUSTRATION),
 
+  // The animation of loading screen to define how long showing on the pages.
+  CONFIG_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS(
+      PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS, ResourceType.INTEGER),
+
+  // The animation for S+ devices used in those screens waiting for non of below type.
+  CONFIG_LOADING_LOTTIE_DEFAULT(
+      PartnerConfigKey.KEY_LOADING_LOTTIE_DEFAULT, ResourceType.ILLUSTRATION),
+
+  // The animation for S+ devices used in those screens which is processing account info or related
+  // functions.
+  // For example:com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT
+  CONFIG_LOADING_LOTTIE_ACCOUNT(
+      PartnerConfigKey.KEY_LOADING_LOTTIE_ACCOUNT, ResourceType.ILLUSTRATION),
+
+  // The animation for S+ devices used in those screens which is processing data connection.
+  // For example:com.android.setupwizard.CAPTIVE_PORTAL
+  CONFIG_LOADING_LOTTIE_CONNECTION(
+      PartnerConfigKey.KEY_LOADING_LOTTIE_CONNECTION, ResourceType.ILLUSTRATION),
+
+  // The animation for S+ devices used in those screens which is updating devices.
+  // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
+  CONFIG_LOADING_LOTTIE_UPDATE(
+      PartnerConfigKey.KEY_LOADING_LOTTIE_UPDATE, ResourceType.ILLUSTRATION),
+
+  // The animation for S+ devices used in those screens which is updating devices.
+  // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
+  CONFIG_LOADING_LOTTIE_FINAL_HOLD(
+      PartnerConfigKey.KEY_LOADING_LOTTIE_FINAL_HOLD, ResourceType.ILLUSTRATION),
+
+  // The transition type to decide the transition between activities or fragments.
+  CONFIG_TRANSITION_TYPE(PartnerConfigKey.KEY_TRANSITION_TYPE, ResourceType.INTEGER),
+
+  // The list of keypath and color map, applied to default animation when light theme.
+  CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_DEFAULT(
+      PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_DEFAULT, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to account animation when light theme.
+  CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_ACCOUNT(
+      PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_ACCOUNT, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to connection animation when light theme.
+  CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_CONNECTION(
+      PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_CONNECTION, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to update animation when light theme.
+  CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_UPDATE(
+      PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_UPDATE, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to update animation when light theme.
+  CONFIG_LOTTIE_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD(
+      PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to default animation when dark theme.
+  CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_DEFAULT(
+      PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_DEFAULT, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to account animation when dark theme.
+  CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_ACCOUNT(
+      PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_ACCOUNT, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to connection animation when dark theme.
+  CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_CONNECTION(
+      PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_CONNECTION, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to update animation when dark theme.
+  CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_UPDATE(
+      PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_UPDATE, ResourceType.STRING_ARRAY),
+
+  // The list of keypath and color map, applied to final hold animation when dark theme.
+  CONFIG_LOTTIE_DARK_THEME_CUSTOMIZATION_FINAL_HOLD(
+      PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_FINAL_HOLD, ResourceType.STRING_ARRAY),
+
+  // The padding top of the content frame of loading layout.
+  CONFIG_LOADING_LAYOUT_PADDING_TOP(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_TOP, ResourceType.DIMENSION),
+
+  // The padding start of the content frame of loading layout.
+  CONFIG_LOADING_LAYOUT_PADDING_START(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_START, ResourceType.DIMENSION),
+
+  // The padding end of the content frame of loading layout.
+  CONFIG_LOADING_LAYOUT_PADDING_END(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END, ResourceType.DIMENSION),
+
+  // The padding bottom of the content frame of loading layout.
+  CONFIG_LOADING_LAYOUT_PADDING_BOTTOM(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM, ResourceType.DIMENSION),
+
+  // The height of the header of the loading layout.
+  CONFIG_LOADING_LAYOUT_HEADER_HEIGHT(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION);
+
+  /** Resource type of the partner resources type. */
   public enum ResourceType {
     INTEGER,
     BOOL,
@@ -195,7 +425,8 @@
     STRING,
     DIMENSION,
     FRACTION,
-    ILLUSTRATION;
+    ILLUSTRATION,
+    STRING_ARRAY
   }
 
   private final String resourceName;
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
index 7b9f65b..2ca8876 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -1,11 +1,11 @@
 /*
- * Copyright 2018 The Android Open Source Project
+ * Copyright (C) 2018 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
+ *      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,
@@ -18,22 +18,27 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
+import android.database.ContentObserver;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.EnumMap;
+import java.util.List;
 
 /** The helper reads and caches the partner configurations from SUW. */
 public class PartnerConfigHelper {
@@ -47,6 +52,22 @@
 
   @VisibleForTesting public static final String KEY_FALLBACK_CONFIG = "fallbackConfig";
 
+  @VisibleForTesting
+  public static final String IS_SUW_DAY_NIGHT_ENABLED_METHOD = "isSuwDayNightEnabled";
+
+  @VisibleForTesting
+  public static final String IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD =
+      "isExtendedPartnerConfigEnabled";
+
+  @VisibleForTesting
+  public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled";
+
+  @VisibleForTesting static Bundle suwDayNightEnabledBundle = null;
+
+  @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null;
+
+  @VisibleForTesting public static Bundle applyDynamicColorBundle = null;
+
   private static PartnerConfigHelper instance = null;
 
   @VisibleForTesting Bundle resultBundle = null;
@@ -54,15 +75,44 @@
   @VisibleForTesting
   final EnumMap<PartnerConfig, Object> partnerResourceCache = new EnumMap<>(PartnerConfig.class);
 
+  private static ContentObserver contentObserver;
+
+  private static int savedConfigUiMode;
+
+  private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT;
+
   public static synchronized PartnerConfigHelper get(@NonNull Context context) {
-    if (instance == null) {
+    if (!isValidInstance(context)) {
       instance = new PartnerConfigHelper(context);
     }
     return instance;
   }
 
+  private static boolean isValidInstance(@NonNull Context context) {
+    Configuration currentConfig = context.getResources().getConfiguration();
+    if (instance == null) {
+      savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+      savedOrientation = currentConfig.orientation;
+      return false;
+    } else {
+      if (isSetupWizardDayNightEnabled(context)
+          && (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) != savedConfigUiMode) {
+        savedConfigUiMode = currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+        resetInstance();
+        return false;
+      } else if (currentConfig.orientation != savedOrientation) {
+        savedOrientation = currentConfig.orientation;
+        resetInstance();
+        return false;
+      }
+    }
+    return true;
+  }
+
   private PartnerConfigHelper(Context context) {
     getPartnerConfigBundle(context);
+
+    registerContentObserver(context);
   }
 
   /**
@@ -75,12 +125,22 @@
   }
 
   /**
+   * Returns whether the given {@code resourceConfig} are available. This is true if setup wizard's
+   * content provider returns us a non-empty bundle, and this result bundle includes the given
+   * {@code resourceConfig} even if all the values are default, and none are customized by the
+   * overlay APK.
+   */
+  public boolean isPartnerConfigAvailable(PartnerConfig resourceConfig) {
+    return isAvailable() && resultBundle.containsKey(resourceConfig.getResourceName());
+  }
+
+  /**
    * Returns the color of given {@code resourceConfig}, or 0 if the given {@code resourceConfig} is
    * not found. If the {@code ResourceType} of the given {@code resourceConfig} is not color,
    * IllegalArgumentException will be thrown.
    *
    * @param context The context of client activity
-   * @param resourceConfig The {@code PartnerConfig} of target resource
+   * @param resourceConfig The {@link PartnerConfig} of target resource
    */
   @ColorInt
   public int getColor(@NonNull Context context, PartnerConfig resourceConfig) {
@@ -99,6 +159,13 @@
       Resources resource = resourceEntry.getResources();
       int resId = resourceEntry.getResourceId();
 
+      // for @null
+      TypedValue outValue = new TypedValue();
+      resource.getValue(resId, outValue, true);
+      if (outValue.type == TypedValue.TYPE_REFERENCE && outValue.data == 0) {
+        return result;
+      }
+
       if (Build.VERSION.SDK_INT >= VERSION_CODES.M) {
         result = resource.getColor(resId, null);
       } else {
@@ -189,6 +256,38 @@
   }
 
   /**
+   * Returns the string array of the given {@code resourceConfig}, or {@code null} if the given
+   * {@code resourceConfig} is not found. If the {@code ResourceType} of the given {@code
+   * resourceConfig} is not string, IllegalArgumentException will be thrown.
+   *
+   * @param context The context of client activity
+   * @param resourceConfig The {@code PartnerConfig} of target resource
+   */
+  @NonNull
+  public List<String> getStringArray(@NonNull Context context, PartnerConfig resourceConfig) {
+    if (resourceConfig.getResourceType() != ResourceType.STRING_ARRAY) {
+      throw new IllegalArgumentException("Not a string array resource");
+    }
+
+    String[] result;
+    List<String> listResult = new ArrayList<>();
+
+    try {
+      ResourceEntry resourceEntry =
+          getResourceEntryFromKey(context, resourceConfig.getResourceName());
+      Resources resource = resourceEntry.getResources();
+      int resId = resourceEntry.getResourceId();
+
+      result = resource.getStringArray(resId);
+      Collections.addAll(listResult, result);
+    } catch (NullPointerException exception) {
+      // fall through
+    }
+
+    return listResult;
+  }
+
+  /**
    * Returns the boolean of given {@code resourceConfig}, or {@code defaultValue} if the given
    * {@code resourceName} is not found. If the {@code ResourceType} of the given {@code
    * resourceConfig} is not boolean, IllegalArgumentException will be thrown.
@@ -233,8 +332,8 @@
   }
 
   /**
-   * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} not
-   * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code
+   * Returns the dimension of given {@code resourceConfig}. If the given {@code resourceConfig} is
+   * not found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code
    * resourceConfig} is not dimension, will throw IllegalArgumentException.
    *
    * @param context The context of client activity
@@ -316,6 +415,39 @@
   }
 
   /**
+   * Returns the integer of given {@code resourceConfig}. If the given {@code resourceConfig} is not
+   * found, will return {@code defaultValue}. If the {@code ResourceType} of given {@code
+   * resourceConfig} is not dimension, will throw IllegalArgumentException.
+   *
+   * @param context The context of client activity
+   * @param resourceConfig The {@code PartnerConfig} of target resource
+   * @param defaultValue The default value
+   */
+  public int getInteger(@NonNull Context context, PartnerConfig resourceConfig, int defaultValue) {
+    if (resourceConfig.getResourceType() != ResourceType.INTEGER) {
+      throw new IllegalArgumentException("Not a integer resource");
+    }
+
+    if (partnerResourceCache.containsKey(resourceConfig)) {
+      return (int) partnerResourceCache.get(resourceConfig);
+    }
+
+    int result = defaultValue;
+    try {
+      ResourceEntry resourceEntry =
+          getResourceEntryFromKey(context, resourceConfig.getResourceName());
+      Resources resource = resourceEntry.getResources();
+      int resId = resourceEntry.getResourceId();
+
+      result = resource.getInteger(resId);
+      partnerResourceCache.put(resourceConfig, result);
+    } catch (NullPointerException exception) {
+      // fall through
+    }
+    return result;
+  }
+
+  /**
    * Returns the {@link ResourceEntry} of given {@code resourceConfig}, or {@code null} if the given
    * {@code resourceConfig} is not found. If the {@link ResourceType} of the given {@code
    * resourceConfig} is not illustration, IllegalArgumentException will be thrown.
@@ -362,17 +494,14 @@
   private void getPartnerConfigBundle(Context context) {
     if (resultBundle == null || resultBundle.isEmpty()) {
       try {
-        Uri contentUri =
-            new Uri.Builder()
-                .scheme(ContentResolver.SCHEME_CONTENT)
-                .authority(SUW_AUTHORITY)
-                .appendPath(SUW_GET_PARTNER_CONFIG_METHOD)
-                .build();
         resultBundle =
             context
                 .getContentResolver()
                 .call(
-                    contentUri, SUW_GET_PARTNER_CONFIG_METHOD, /* arg= */ null, /* extras= */ null);
+                    getContentUri(),
+                    SUW_GET_PARTNER_CONFIG_METHOD,
+                    /* arg= */ null,
+                    /* extras= */ null);
         partnerResourceCache.clear();
       } catch (IllegalArgumentException | SecurityException exception) {
         Log.w(TAG, "Fail to get config from suw provider");
@@ -381,21 +510,134 @@
   }
 
   @Nullable
-  private ResourceEntry getResourceEntryFromKey(Context context, String resourceName) {
+  @VisibleForTesting
+  ResourceEntry getResourceEntryFromKey(Context context, String resourceName) {
     Bundle resourceEntryBundle = resultBundle.getBundle(resourceName);
     Bundle fallbackBundle = resultBundle.getBundle(KEY_FALLBACK_CONFIG);
     if (fallbackBundle != null) {
       resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName));
     }
-    return ResourceEntry.fromBundle(context, resourceEntryBundle);
+
+    return adjustResourceEntryDayNightMode(
+        context, ResourceEntry.fromBundle(context, resourceEntryBundle));
+  }
+
+  /**
+   * Force to day mode if setup wizard does not support day/night mode and current system is in
+   * night mode.
+   */
+  private static ResourceEntry adjustResourceEntryDayNightMode(
+      Context context, ResourceEntry resourceEntry) {
+    Resources resource = resourceEntry.getResources();
+    Configuration configuration = resource.getConfiguration();
+    if (!isSetupWizardDayNightEnabled(context) && Util.isNightMode(configuration)) {
+      if (resourceEntry == null) {
+        Log.w(TAG, "resourceEntry is null, skip to force day mode.");
+        return resourceEntry;
+      }
+      configuration.uiMode =
+          Configuration.UI_MODE_NIGHT_NO
+              | (configuration.uiMode & ~Configuration.UI_MODE_NIGHT_MASK);
+      resource.updateConfiguration(configuration, resource.getDisplayMetrics());
+    }
+
+    return resourceEntry;
   }
 
   @VisibleForTesting
-  public static synchronized void resetForTesting() {
+  public static synchronized void resetInstance() {
     instance = null;
+    suwDayNightEnabledBundle = null;
+    applyExtendedPartnerConfigBundle = null;
+    applyDynamicColorBundle = null;
   }
 
-  private TypedValue getTypedValueFromResource(Resources resource, int resId, int type) {
+  /**
+   * Checks whether SetupWizard supports the DayNight theme during setup flow; if return false setup
+   * flow should force to light theme.
+   *
+   * <p>Returns true if the setupwizard is listening to system DayNight theme setting.
+   */
+  public static boolean isSetupWizardDayNightEnabled(@NonNull Context context) {
+    if (suwDayNightEnabledBundle == null) {
+      try {
+        suwDayNightEnabledBundle =
+            context
+                .getContentResolver()
+                .call(
+                    getContentUri(),
+                    IS_SUW_DAY_NIGHT_ENABLED_METHOD,
+                    /* arg= */ null,
+                    /* extras= */ null);
+      } catch (IllegalArgumentException | SecurityException exception) {
+        Log.w(TAG, "SetupWizard DayNight supporting status unknown; return as false.");
+        suwDayNightEnabledBundle = null;
+        return false;
+      }
+    }
+
+    return (suwDayNightEnabledBundle != null
+        && suwDayNightEnabledBundle.getBoolean(IS_SUW_DAY_NIGHT_ENABLED_METHOD, false));
+  }
+
+  /** Returns true if the SetupWizard supports the extended partner configs during setup flow. */
+  public static boolean shouldApplyExtendedPartnerConfig(@NonNull Context context) {
+    if (applyExtendedPartnerConfigBundle == null) {
+      try {
+        applyExtendedPartnerConfigBundle =
+            context
+                .getContentResolver()
+                .call(
+                    getContentUri(),
+                    IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD,
+                    /* arg= */ null,
+                    /* extras= */ null);
+      } catch (IllegalArgumentException | SecurityException exception) {
+        Log.w(
+            TAG,
+            "SetupWizard extended partner configs supporting status unknown; return as false.");
+        applyExtendedPartnerConfigBundle = null;
+        return false;
+      }
+    }
+
+    return (applyExtendedPartnerConfigBundle != null
+        && applyExtendedPartnerConfigBundle.getBoolean(
+            IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false));
+  }
+
+  /** Returns true if the SetupWizard supports the dynamic color during setup flow. */
+  public static boolean isSetupWizardDynamicColorEnabled(@NonNull Context context) {
+    if (applyDynamicColorBundle == null) {
+      try {
+        applyDynamicColorBundle =
+            context
+                .getContentResolver()
+                .call(
+                    getContentUri(),
+                    IS_DYNAMIC_COLOR_ENABLED_METHOD,
+                    /* arg= */ null,
+                    /* extras= */ null);
+      } catch (IllegalArgumentException | SecurityException exception) {
+        Log.w(TAG, "SetupWizard dynamic color supporting status unknown; return as false.");
+        applyDynamicColorBundle = null;
+        return false;
+      }
+    }
+
+    return (applyDynamicColorBundle != null
+        && applyDynamicColorBundle.getBoolean(IS_DYNAMIC_COLOR_ENABLED_METHOD, false));
+  }
+
+  @VisibleForTesting
+  static Uri getContentUri() {
+    return new Uri.Builder()
+        .scheme(ContentResolver.SCHEME_CONTENT)
+        .authority(SUW_AUTHORITY)
+        .build();
+  }
+
+  private static TypedValue getTypedValueFromResource(Resources resource, int resId, int type) {
     TypedValue value = new TypedValue();
     resource.getValue(resId, value, true);
     if (value.type != type) {
@@ -409,8 +651,42 @@
     return value;
   }
 
-  private float getDimensionFromTypedValue(Context context, TypedValue value) {
+  private static float getDimensionFromTypedValue(Context context, TypedValue value) {
     DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
     return value.getDimension(displayMetrics);
   }
+
+  private static void registerContentObserver(Context context) {
+    if (isSetupWizardDayNightEnabled(context)) {
+      if (contentObserver != null) {
+        unregisterContentObserver(context);
+      }
+
+      Uri contentUri = getContentUri();
+      try {
+        contentObserver =
+            new ContentObserver(null) {
+              @Override
+              public void onChange(boolean selfChange) {
+                super.onChange(selfChange);
+                resetInstance();
+              }
+            };
+        context
+            .getContentResolver()
+            .registerContentObserver(contentUri, /* notifyForDescendants= */ true, contentObserver);
+      } catch (SecurityException | NullPointerException | IllegalArgumentException e) {
+        Log.w(TAG, "Failed to register content observer for " + contentUri + ": " + e);
+      }
+    }
+  }
+
+  private static void unregisterContentObserver(Context context) {
+    try {
+      context.getContentResolver().unregisterContentObserver(contentObserver);
+      contentObserver = null;
+    } catch (SecurityException | NullPointerException | IllegalArgumentException e) {
+      Log.w(TAG, "Failed to unregister content observer: " + e);
+    }
+  }
 }
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index e5c5442..c7444a5 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -28,7 +28,9 @@
   PartnerConfigKey.KEY_LIGHT_STATUS_BAR,
   PartnerConfigKey.KEY_NAVIGATION_BAR_BG_COLOR,
   PartnerConfigKey.KEY_LIGHT_NAVIGATION_BAR,
+  PartnerConfigKey.KEY_NAVIGATION_BAR_DIVIDER_COLOR,
   PartnerConfigKey.KEY_FOOTER_BAR_BG_COLOR,
+  PartnerConfigKey.KEY_FOOTER_BAR_MIN_HEIGHT,
   PartnerConfigKey.KEY_FOOTER_BUTTON_FONT_FAMILY,
   PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_ADD_ANOTHER,
   PartnerConfigKey.KEY_FOOTER_BUTTON_ICON_CANCEL,
@@ -43,6 +45,8 @@
   PartnerConfigKey.KEY_FOOTER_BUTTON_RADIUS,
   PartnerConfigKey.KEY_FOOTER_BUTTON_RIPPLE_ALPHA,
   PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_SIZE,
+  PartnerConfigKey.KEY_FOOTER_BUTTON_TEXT_STYLE,
+  PartnerConfigKey.KEY_FOOTER_BUTTON_MIN_HEIGHT,
   PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_ALPHA,
   PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR,
   PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR,
@@ -50,25 +54,80 @@
   PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR,
   PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
   PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR,
+  PartnerConfigKey.KEY_LAYOUT_MARGIN_START,
+  PartnerConfigKey.KEY_LAYOUT_MARGIN_END,
   PartnerConfigKey.KEY_HEADER_TEXT_SIZE,
   PartnerConfigKey.KEY_HEADER_TEXT_COLOR,
   PartnerConfigKey.KEY_HEADER_FONT_FAMILY,
   PartnerConfigKey.KEY_HEADER_AREA_BACKGROUND_COLOR,
+  PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_TOP,
+  PartnerConfigKey.KEY_HEADER_TEXT_MARGIN_BOTTOM,
+  PartnerConfigKey.KEY_HEADER_CONTAINER_MARGIN_BOTTOM,
+  PartnerConfigKey.KEY_HEADER_AUTO_SIZE_ENABLED,
+  PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_TEXT_SIZE,
+  PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MIN_TEXT_SIZE,
+  PartnerConfigKey.KEY_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE,
+  PartnerConfigKey.KEY_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA,
   PartnerConfigKey.KEY_LAYOUT_GRAVITY,
+  PartnerConfigKey.KEY_ICON_MARGIN_TOP,
+  PartnerConfigKey.KEY_ICON_SIZE,
   PartnerConfigKey.KEY_DESCRIPTION_TEXT_SIZE,
   PartnerConfigKey.KEY_DESCRIPTION_TEXT_COLOR,
   PartnerConfigKey.KEY_DESCRIPTION_LINK_TEXT_COLOR,
   PartnerConfigKey.KEY_DESCRIPTION_FONT_FAMILY,
+  PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_TOP,
+  PartnerConfigKey.KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM,
   PartnerConfigKey.KEY_CONTENT_TEXT_SIZE,
   PartnerConfigKey.KEY_CONTENT_TEXT_COLOR,
   PartnerConfigKey.KEY_CONTENT_LINK_TEXT_COLOR,
   PartnerConfigKey.KEY_CONTENT_FONT_FAMILY,
   PartnerConfigKey.KEY_CONTENT_LAYOUT_GRAVITY,
+  PartnerConfigKey.KEY_CONTENT_PADDING_TOP,
+  PartnerConfigKey.KEY_CONTENT_INFO_TEXT_SIZE,
+  PartnerConfigKey.KEY_CONTENT_INFO_FONT_FAMILY,
+  PartnerConfigKey.KEY_CONTENT_INFO_LINE_SPACING_EXTRA,
+  PartnerConfigKey.KEY_CONTENT_INFO_ICON_SIZE,
+  PartnerConfigKey.KEY_CONTENT_INFO_ICON_MARGIN_END,
+  PartnerConfigKey.KEY_CONTENT_INFO_PADDING_TOP,
+  PartnerConfigKey.KEY_CONTENT_INFO_PADDING_BOTTOM,
+  PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH,
+  PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT,
+  PartnerConfigKey.KEY_ITEMS_TITLE_TEXT_SIZE,
+  PartnerConfigKey.KEY_ITEMS_SUMMARY_TEXT_SIZE,
+  PartnerConfigKey.KEY_ITEMS_SUMMARY_MARGIN_TOP,
+  PartnerConfigKey.KEY_ITEMS_TITLE_FONT_FAMILY,
+  PartnerConfigKey.KEY_ITEMS_SUMMARY_FONT_FAMILY,
+  PartnerConfigKey.KEY_ITEMS_PADDING_TOP,
+  PartnerConfigKey.KEY_ITEMS_PADDING_BOTTOM,
+  PartnerConfigKey.KEY_ITEMS_MIN_HEIGHT,
+  PartnerConfigKey.KEY_ITEMS_DIVIDER_SHOWN,
   PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DEFAULT,
   PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_ACCOUNT,
   PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_CONNECTION,
   PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_UPDATE,
+  PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_FINAL_HOLD,
   PartnerConfigKey.KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS,
+  PartnerConfigKey.KEY_LOADING_LOTTIE_ACCOUNT,
+  PartnerConfigKey.KEY_LOADING_LOTTIE_CONNECTION,
+  PartnerConfigKey.KEY_LOADING_LOTTIE_DEFAULT,
+  PartnerConfigKey.KEY_LOADING_LOTTIE_UPDATE,
+  PartnerConfigKey.KEY_LOADING_LOTTIE_FINAL_HOLD,
+  PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_DEFAULT,
+  PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_ACCOUNT,
+  PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_CONNECTION,
+  PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_UPDATE,
+  PartnerConfigKey.KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD,
+  PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_DEFAULT,
+  PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_ACCOUNT,
+  PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_CONNECTION,
+  PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_UPDATE,
+  PartnerConfigKey.KEY_LOADING_DARK_THEME_CUSTOMIZATION_FINAL_HOLD,
+  PartnerConfigKey.KEY_TRANSITION_TYPE,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_TOP,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_START,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT,
 })
 // TODO: can be removed and always reference PartnerConfig.getResourceName()?
 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
@@ -87,9 +146,15 @@
   // such that it is compatible with a light navigation bar background.
   String KEY_LIGHT_NAVIGATION_BAR = "setup_compat_light_navigation_bar";
 
+  // Navigation bar divider color
+  String KEY_NAVIGATION_BAR_DIVIDER_COLOR = "setup_compat_navigation_bar_divider_color";
+
   // Background color of the footer bar.
   String KEY_FOOTER_BAR_BG_COLOR = "setup_compat_footer_bar_bg_color";
 
+  // The min height of the footer bar
+  String KEY_FOOTER_BAR_MIN_HEIGHT = "setup_compat_footer_bar_min_height";
+
   // 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";
@@ -127,12 +192,18 @@
   // Corner radius of the footer buttons
   String KEY_FOOTER_BUTTON_RADIUS = "setup_compat_footer_button_radius";
 
-  // Ripple color alpha of the footer button
+  // Ripple color alpha of the footer buttons
   String KEY_FOOTER_BUTTON_RIPPLE_ALPHA = "setup_compat_footer_button_ripple_alpha";
 
-  // Text size of the footer button
+  // Text size of the footer buttons
   String KEY_FOOTER_BUTTON_TEXT_SIZE = "setup_compat_footer_button_text_size";
 
+  // The font face used in footer buttons {0 = NORMAL}, {1 = BOLD}, {2 = ITALIC}, {3 = BOLD_ITALIC}
+  String KEY_FOOTER_BUTTON_TEXT_STYLE = "setup_compat_footer_button_text_style";
+
+  // The min height of the footer buttons
+  String KEY_FOOTER_BUTTON_MIN_HEIGHT = "setup_compat_footer_button_min_height";
+
   // Disabled background alpha of the footer buttons
   String KEY_FOOTER_BUTTON_DISABLED_ALPHA = "setup_compat_footer_button_disabled_alpha";
 
@@ -154,6 +225,12 @@
   // Background color of layout
   String KEY_LAYOUT_BACKGROUND_COLOR = "setup_design_layout_bg_color";
 
+  // Margin start of the layout
+  String KEY_LAYOUT_MARGIN_START = "setup_design_layout_margin_start";
+
+  // Margin end of the layout
+  String KEY_LAYOUT_MARGIN_END = "setup_design_layout_margin_end";
+
   // Text size of the header
   String KEY_HEADER_TEXT_SIZE = "setup_design_header_text_size";
 
@@ -163,12 +240,44 @@
   // Font family of the header
   String KEY_HEADER_FONT_FAMILY = "setup_design_header_font_family";
 
+  // Margin top of the header text
+  String KEY_HEADER_TEXT_MARGIN_TOP = "setup_design_header_text_margin_top";
+
+  // Margin bottom of the header text
+  String KEY_HEADER_TEXT_MARGIN_BOTTOM = "setup_design_header_text_margin_bottom";
+
   // Gravity of the header, icon and description
   String KEY_LAYOUT_GRAVITY = "setup_design_layout_gravity";
 
+  // Margin top of the icon
+  String KEY_ICON_MARGIN_TOP = "setup_design_icon_margin_top";
+
+  // Size of the icon
+  String KEY_ICON_SIZE = "setup_design_icon_size";
+
   // Background color of the header area
   String KEY_HEADER_AREA_BACKGROUND_COLOR = "setup_design_header_area_background_color";
 
+  // Margin bottom of the header container
+  String KEY_HEADER_CONTAINER_MARGIN_BOTTOM = "setup_design_header_container_margin_bottom";
+
+  // Auto text size enabled status
+  String KEY_HEADER_AUTO_SIZE_ENABLED = "setup_design_header_auto_size_enabled";
+
+  // Max text size of header when auto size enabled. Ignored if auto size is false.
+  String KEY_HEADER_AUTO_SIZE_MAX_TEXT_SIZE = "setup_design_header_auto_size_max_text_size";
+
+  // Min text size of header when auto size enabled. Ignored if auto size is false.
+  String KEY_HEADER_AUTO_SIZE_MIN_TEXT_SIZE = "setup_design_header_auto_size_min_text_size";
+
+  // The max lines of the max text size when auto size enabled. Ignored if auto size is false.
+  String KEY_HEADER_AUTO_SIZE_MAX_LINE_OF_MAX_SIZE =
+      "setup_design_header_auto_size_max_line_of_max_size";
+
+  // Extra line spacing of header when auto size enabled. Ignored if auto size is false.
+  String KEY_HEADER_AUTO_SIZE_LINE_SPACING_EXTRA =
+      "setup_design_header_auto_size_line_spacing_extra";
+
   // Text size of the description
   String KEY_DESCRIPTION_TEXT_SIZE = "setup_design_description_text_size";
 
@@ -181,6 +290,12 @@
   // Font family of the description
   String KEY_DESCRIPTION_FONT_FAMILY = "setup_design_description_font_family";
 
+  // Margin top of the header text
+  String KEY_DESCRIPTION_TEXT_MARGIN_TOP = "setup_design_description_text_margin_top";
+
+  // Margin bottom of the header text
+  String KEY_DESCRIPTION_TEXT_MARGIN_BOTTOM = "setup_design_description_text_margin_bottom";
+
   // Text size of the body content text
   String KEY_CONTENT_TEXT_SIZE = "setup_design_content_text_size";
 
@@ -196,6 +311,63 @@
   // Gravity of the body content text
   String KEY_CONTENT_LAYOUT_GRAVITY = "setup_design_content_layout_gravity";
 
+  // The padding top of the content
+  String KEY_CONTENT_PADDING_TOP = "setup_design_content_padding_top";
+
+  // The text size of the content info.
+  String KEY_CONTENT_INFO_TEXT_SIZE = "setup_design_content_info_text_size";
+
+  // The font family of the content info.
+  String KEY_CONTENT_INFO_FONT_FAMILY = "setup_design_content_info_font_family";
+
+  // The text line spacing extra of the content info.
+  String KEY_CONTENT_INFO_LINE_SPACING_EXTRA = "setup_design_content_info_line_spacing_extra";
+
+  // The icon size of the content info.
+  String KEY_CONTENT_INFO_ICON_SIZE = "setup_design_content_info_icon_size";
+
+  // The icon margin end of the content info.
+  String KEY_CONTENT_INFO_ICON_MARGIN_END = "setup_design_content_info_icon_margin_end";
+
+  // The padding top of the content info.
+  String KEY_CONTENT_INFO_PADDING_TOP = "setup_design_content_info_padding_top";
+
+  // The padding bottom of the content info.
+  String KEY_CONTENT_INFO_PADDING_BOTTOM = "setup_design_content_info_padding_bottom";
+
+  // The title text size of list items.
+  String KEY_ITEMS_TITLE_TEXT_SIZE = "setup_design_items_title_text_size";
+
+  // The summary text size of list items.
+  String KEY_ITEMS_SUMMARY_TEXT_SIZE = "setup_design_items_summary_text_size";
+
+  // The summary margin top of list items.
+  String KEY_ITEMS_SUMMARY_MARGIN_TOP = "setup_design_items_summary_margin_top";
+
+  // The title font family of list items.
+  String KEY_ITEMS_TITLE_FONT_FAMILY = "setup_design_items_title_font_family";
+
+  // The summary font family of list items.
+  String KEY_ITEMS_SUMMARY_FONT_FAMILY = "setup_design_items_summary_font_family";
+
+  // The padding top of list items.
+  String KEY_ITEMS_PADDING_TOP = "setup_design_items_padding_top";
+
+  // The padding bottom of list items.
+  String KEY_ITEMS_PADDING_BOTTOM = "setup_design_items_padding_bottom";
+
+  // The minimum height of list items.
+  String KEY_ITEMS_MIN_HEIGHT = "setup_design_items_min_height";
+
+  // The divider of list items are showing.
+  String KEY_ITEMS_DIVIDER_SHOWN = "setup_design_items_divider_shown";
+
+  // The intrinsic width of the card view for foldabe/tablet.
+  String KEY_CARD_VIEW_INTRINSIC_WIDTH = "setup_design_card_view_intrinsic_width";
+
+  // The intrinsic height of the card view for foldabe/tablet.
+  String KEY_CARD_VIEW_INTRINSIC_HEIGHT = "setup_design_card_view_intrinsic_height";
+
   // The animation of loading screen used in those activities which is non of below type.
   String KEY_PROGRESS_ILLUSTRATION_DEFAULT = "progress_illustration_custom_default";
 
@@ -212,6 +384,94 @@
   // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
   String KEY_PROGRESS_ILLUSTRATION_UPDATE = "progress_illustration_custom_update";
 
+  // The animation of loading screen used in those activities which is updating device.
+  // For example:com.google.android.setupwizard.FINAL_HOLD
+  String KEY_PROGRESS_ILLUSTRATION_FINAL_HOLD = "final_hold_custom_illustration";
+
   // The minimum illustration display time, set to 0 may cause the illustration stuck
   String KEY_PROGRESS_ILLUSTRATION_DISPLAY_MINIMUM_MS = "progress_illustration_display_minimum_ms";
+
+  // The animation for S+ devices used in those screens waiting for non of below type.
+  String KEY_LOADING_LOTTIE_DEFAULT = "loading_animation_custom_default";
+
+  // The animation for S+ devices used in those screens which is processing account info or related
+  // functions.
+  // For example:com.google.android.setupwizard.LOAD_ADD_ACCOUNT_INTENT
+  String KEY_LOADING_LOTTIE_ACCOUNT = "loading_animation_custom_account";
+
+  // The animation for S+ devices used in those screens which is processing data connection.
+  // For example:com.android.setupwizard.CAPTIVE_PORTAL
+  String KEY_LOADING_LOTTIE_CONNECTION = "loading_animation_custom_connection";
+
+  // The animation for S+ devices used in those screens which is updating devices.
+  // For example:com.google.android.setupwizard.COMPAT_EARLY_UPDATE
+  String KEY_LOADING_LOTTIE_UPDATE = "loading_animation_custom_update";
+
+  // The animation for S+ devices used in those screens which is updating devices.
+  // For example:com.google.android.setupwizard.FINAL_HOLD
+  String KEY_LOADING_LOTTIE_FINAL_HOLD = "loading_animation_custom_final_hold";
+
+  // A string-array to list all the key path and color map for default animation for light theme.
+  // For example: background:#FFFFFF
+  String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_DEFAULT =
+      "loading_light_theme_customization_default";
+
+  // A string-array to list all the key path and color map for account animation for light theme.
+  // For example: background:#FFFFFF
+  String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_ACCOUNT =
+      "loading_light_theme_customization_account";
+
+  // A string-array to list all the key path and color map for connection animation for light theme.
+  // For example: background:#FFFFFF
+  String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_CONNECTION =
+      "loading_light_theme_customization_connection";
+
+  // A string-array to list all the key path and color map for update animation for light theme.
+  // For example: background:#FFFFFF
+  String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_UPDATE = "loading_light_theme_customization_update";
+
+  // A string-array to list all the key path and color map for final hold animation for light theme.
+  // For example: background:#FFFFFF
+  String KEY_LOADING_LIGHT_THEME_CUSTOMIZATION_FINAL_HOLD =
+      "loading_light_theme_customization_final_hold";
+
+  // A string-array to list all the key path and color map for default animation for dark theme.
+  // For example: background:#000000
+  String KEY_LOADING_DARK_THEME_CUSTOMIZATION_DEFAULT = "loading_dark_theme_customization_default";
+
+  // A string-array to list all the key path and color map for account animation for dark theme.
+  // For example: background:#000000
+  String KEY_LOADING_DARK_THEME_CUSTOMIZATION_ACCOUNT = "loading_dark_theme_customization_account";
+
+  // A string-array to list all the key path and color map for connection animation for dark theme.
+  // For example: background:#000000
+  String KEY_LOADING_DARK_THEME_CUSTOMIZATION_CONNECTION =
+      "loading_dark_theme_customization_connection";
+
+  // A string-array to list all the key path and color map for update animation for dark theme.
+  // For example: background:#000000
+  String KEY_LOADING_DARK_THEME_CUSTOMIZATION_UPDATE = "loading_dark_theme_customization_update";
+
+  // A string-array to list all the key path and color map for final hold animation for dark theme.
+  // For example: background:#000000
+  String KEY_LOADING_DARK_THEME_CUSTOMIZATION_FINAL_HOLD =
+      "loading_dark_theme_customization_final_hold";
+
+  // The transition type between activities
+  String KEY_TRANSITION_TYPE = "setup_design_transition_type";
+
+  // A padding top of the content frame of loading layout.
+  String KEY_LOADING_LAYOUT_CONTENT_PADDING_TOP = "loading_layout_content_padding_top";
+
+  // A padding start of the content frame of loading layout.
+  String KEY_LOADING_LAYOUT_CONTENT_PADDING_START = "loading_layout_content_padding_start";
+
+  // A padding end of the content frame of loading layout.
+  String KEY_LOADING_LAYOUT_CONTENT_PADDING_END = "loading_layout_content_padding_end";
+
+  // A padding bottom of the content frame of loading layout.
+  String KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM = "loading_layout_content_padding_bottom";
+
+  // A height of the header of loading layout.
+  String KEY_LOADING_LAYOUT_HEADER_HEIGHT = "loading_layout_header_height";
 }
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java
index 8f7c9d8..c8b8623 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/ResourceEntry.java
@@ -23,10 +23,10 @@
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
 import android.os.Bundle;
+import android.util.Log;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import android.util.Log;
 
 /**
  * A potentially cross-package resource entry, which can then be retrieved using {@link
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java
new file mode 100644
index 0000000..8ac4c2d
--- /dev/null
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/Util.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 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.partnerconfig;
+
+import android.content.res.Configuration;
+
+/** The utility to help partner to config. */
+public final class Util {
+
+  public static boolean isNightMode(Configuration configuration) {
+    return (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+        == Configuration.UI_MODE_NIGHT_YES;
+  }
+
+  private Util() {}
+}