Import updated Android SetupCompat Library 682227189

Copied from google3/third_party/java_src/android_libs/setupcompat

Test: mm

Included changes:
  - 682227189 [BC25] Fix NPE when device in the 2 pane mode.
  - 681811849 [BC25] Refine down button position in landscape mode. (2/2)
  - 681778893 [BC25] Make the button bar with can be in the whole scree...
  - 681309180 [BC25][Down Button] Fix the issue that the button width i...
  - 680892642 [BC25] Using footer bar width to calcuate the button widt...
  - 680884916 Create the keyboard focus change flag.
  - 680797017 Automated g4 rollback of changelist 671307433.
  - 680440592 [BC25] Add sucFooterBarButtonFontWeight attribute and set...
  - 679015292 [BC25] remove using the attribute sucFullDynamicColor as ...
  - 678654714 Create item group corner Radius Partner Config.
  - 678474263 [BC25] Apply the button width to expressive style if scro...
  - 676755151 [SUW] When GlifExpressive flag to PartnerConfigHelper wil...
  - 675803166 [BC25] Add margin to footer button in expressive style
  - 674997096 [BC25] Add sucFooterBarMinHeight attribute to adjust the ...
  - 674083566 [BC25] Add MaterialButton support for FooterBarMixin
  - 673750749 [BC25] Add footer button text color for Glif expressive s...
  - 672458843 [BC25] Set button width for expressive style
  - 672361102 [BC25] Update primary button color for Glif expressive st...
  - 671581134 [BC25] Add setVisibility() method to FooterButton.Builder
  - 671307433 Automated g4 rollback of changelist 671261504.
  - 671261504 Remove the deprecated component CompatEnforcer, due to th...
  - 671255468 [BC25] Add IFooterActionButton interface and used in Foot...
  - 671156324 [BC25] Add MaterialFooterActionButton
  - 668397555 [BC25] Add IFooterActionButton interface to indicate Andr...
  - 667870529 [BC25] Guard addSpace when GlifExpressive flag enabled.
  - 666114642 [Theme] Add Glif Expressive theme in ThemeHelper and Them...
  - 663516526 [BC25] Implement isGlifExpressiveEnabled method in SetupC...
  - 661990826 Fix typo in PartnerConfigHelper.isGlifExpressiveEnabled()...
  - 660701130 [BC25][Theme] Add isGlifExpressEnabled method to PartnerC...
  - 657141370 Create SetupWizardOrchestratorViewModel.
  - 656277701 Small fix for passing logging observer from PartnerCustom...

PiperOrigin-RevId: 682227189
Bug: 344740755
Change-Id: I8749422c83edc23a6e725d9806314b20e45fd309
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index 26e7042..fff4c08 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -309,6 +309,10 @@
    * {@code false}.
    */
   public boolean shouldApplyDynamicColor() {
+    if (!PartnerConfigHelper.isSetupWizardDynamicColorEnabled(getContext())) {
+      return false;
+    }
+
     if (!useDynamicColor) {
       return false;
     }
@@ -323,23 +327,23 @@
 
   /**
    * 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}.
+   * returns {@code false}. This method combines the result of {@link #shouldApplyDynamicColor()},
+   * the value of the {@code app:sucFullDynamicColor}, and the result of {@link
+   * PartnerConfigHelper#isSetupWizardFullDynamicColorEnabled(Context)}.
    */
   public boolean useFullDynamicColor() {
-    return shouldApplyDynamicColor() && useFullDynamicColorAttr;
+    return shouldApplyDynamicColor()
+        && (useFullDynamicColorAttr
+            || PartnerConfigHelper.isSetupWizardFullDynamicColorEnabled(getContext()));
   }
 
   /**
-   * Sets a logging observer for {@link FooterBarMixin}. The logging observer is used to log
-   * impressions and clicks on the layout and footer bar buttons.
-   *
-   * @throws UnsupportedOperationException if the primary or secondary button has been set before
-   *     the logging observer is set
+   * Sets a logging observer for {@link FooterBarMixin}. The logging observer is used to log UI
+   * events (e.g. page impressions and button clicks) on the layout and footer bar buttons.
    */
   public void setLoggingObserver(LoggingObserver loggingObserver) {
-    getMixin(FooterBarMixin.class).setLoggingObserver(loggingObserver);
     loggingObserver.log(new LayoutInflatedEvent(this));
+    getMixin(FooterBarMixin.class).setLoggingObserver(loggingObserver);
   }
 
   /**
diff --git a/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java b/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java
index d882c9d..733ee52 100644
--- a/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java
+++ b/main/java/com/google/android/setupcompat/internal/LifecycleFragment.java
@@ -52,10 +52,6 @@
    */
   public static LifecycleFragment attachNow(Activity activity) {
     if (WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
-      SetupCompatServiceInvoker.get(activity.getApplicationContext())
-          .bindBack(
-              LayoutBindBackHelper.getScreenName(activity),
-              LayoutBindBackHelper.getExtraBundle(activity));
 
       if (VERSION.SDK_INT > VERSION_CODES.M) {
         FragmentManager fragmentManager = activity.getFragmentManager();
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
index ed9c0e3..7208778 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
@@ -32,8 +32,7 @@
 /**
  * This class is responsible for safely executing methods on SetupCompatService. To avoid memory
  * issues due to backed up queues, an upper bound of {@link
- * ExecutorProvider#SETUP_METRICS_LOGGER_MAX_QUEUED} is set on the logging executor service's queue
- * and {@link ExecutorProvider#SETUP_COMPAT_BINDBACK_MAX_QUEUED} on the overall executor service.
+ * ExecutorProvider#SETUP_METRICS_LOGGER_MAX_QUEUED} is set on the logging executor service's queue.
  * Once the upper bound is reached, metrics published after this event are dropped silently.
  *
  * <p>NOTE: This class is not meant to be used directly. Please use {@link
@@ -52,14 +51,6 @@
     }
   }
 
-  public void bindBack(String screenName, Bundle bundle) {
-    try {
-      loggingExecutor.execute(() -> invokeBindBack(screenName, bundle));
-    } catch (RejectedExecutionException e) {
-      LOG.e(String.format("Screen %s bind back fail.", screenName), e);
-    }
-  }
-
   /**
    * Help invoke the {@link ISetupCompatService#onFocusStatusChanged} using {@code loggingExecutor}.
    */
@@ -110,23 +101,6 @@
     }
   }
 
-  private void invokeBindBack(String screenName, Bundle bundle) {
-    try {
-      ISetupCompatService setupCompatService =
-          SetupCompatServiceProvider.get(
-              context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
-      if (setupCompatService != null) {
-        setupCompatService.validateActivity(screenName, bundle);
-      } else {
-        LOG.w("BindBack failed since service reference is null. Are the permissions valid?");
-      }
-    } catch (InterruptedException | TimeoutException | RemoteException e) {
-      LOG.e(
-          String.format("Exception occurred while %s trying bind back to SetupWizard.", screenName),
-          e);
-    }
-  }
-
   private SetupCompatServiceInvoker(Context context) {
     this.context = context;
     this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
diff --git a/main/java/com/google/android/setupcompat/template/FooterActionButton.java b/main/java/com/google/android/setupcompat/template/FooterActionButton.java
index d9726f9..212f86e 100644
--- a/main/java/com/google/android/setupcompat/template/FooterActionButton.java
+++ b/main/java/com/google/android/setupcompat/template/FooterActionButton.java
@@ -22,12 +22,13 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.Button;
-import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 /** Button that can react to touch when disabled. */
-public class FooterActionButton extends Button {
+@SuppressWarnings("AppCompatCustomView")
+public class FooterActionButton extends Button implements IFooterActionButton {
 
-  @Nullable private FooterButton footerButton;
+  @VisibleForTesting FooterButton footerButton;
   private boolean isPrimaryButtonStyle = false;
 
   public FooterActionButton(Context context, AttributeSet attrs) {
@@ -38,8 +39,6 @@
     this.footerButton = footerButton;
   }
 
-  // getOnClickListenerWhenDisabled is responsible for handling accessibility correctly, calling
-  // performClick if necessary.
   @SuppressLint("ClickableViewAccessibility")
   @Override
   public boolean onTouchEvent(MotionEvent event) {
@@ -47,6 +46,8 @@
       if (footerButton != null
           && !footerButton.isEnabled()
           && footerButton.getVisibility() == View.VISIBLE) {
+        // getOnClickListenerWhenDisabled is responsible for handling accessibility correctly,
+        // calling performClick if necessary.
         OnClickListener listener = footerButton.getOnClickListenerWhenDisabled();
         if (listener != null) {
           listener.onClick(this);
@@ -65,7 +66,7 @@
     this.isPrimaryButtonStyle = isPrimaryButtonStyle;
   }
 
-  /** Returns true when the footer button is primary button style. */
+  @Override
   public boolean isPrimaryButtonStyle() {
     return isPrimaryButtonStyle;
   }
diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index 5939a18..e52b136 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -25,6 +25,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Color;
+import android.graphics.drawable.GradientDrawable;
 import android.os.Build.VERSION_CODES;
 import android.os.PersistableBundle;
 import android.util.AttributeSet;
@@ -34,6 +35,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.widget.Button;
 import android.widget.LinearLayout;
 import android.widget.LinearLayout.LayoutParams;
@@ -47,6 +50,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StyleRes;
 import androidx.annotation.VisibleForTesting;
+import com.google.android.material.button.MaterialButton;
 import com.google.android.setupcompat.PartnerCustomizationLayout;
 import com.google.android.setupcompat.R;
 import com.google.android.setupcompat.internal.FooterButtonPartnerConfig;
@@ -57,6 +61,7 @@
 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
 import com.google.android.setupcompat.template.FooterButton.ButtonType;
+import com.google.android.setupcompat.util.Logger;
 import java.util.Locale;
 
 /**
@@ -66,6 +71,8 @@
  */
 public class FooterBarMixin implements Mixin {
 
+  private static final Logger LOG = new Logger("FooterBarMixin");
+
   private final Context context;
 
   @Nullable private final ViewStub footerStub;
@@ -93,6 +100,11 @@
   @ColorInt private final int footerBarSecondaryBackgroundColor;
   private boolean removeFooterBarWhenEmpty = true;
   private boolean isSecondaryButtonInPrimaryStyle = false;
+  private final int footerBarPrimaryButtonEnabledTextColor;
+  private final int footerBarSecondaryButtonEnabledTextColor;
+  private final int footerBarPrimaryButtonDisabledTextColor;
+  private final int footerBarSecondaryButtonDisabledTextColor;
+  @VisibleForTesting final int footerBarButtonMiddleSpacing;
 
   @VisibleForTesting public final FooterBarMixinMetrics metrics = new FooterBarMixinMetrics();
 
@@ -106,16 +118,35 @@
           Button button = buttonContainer.findViewById(id);
           if (button != null) {
             button.setEnabled(enabled);
-            if (applyPartnerResources && !applyDynamicColor) {
 
-              updateButtonTextColorWithStates(
-                  button,
-                  (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
-                      ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
-                      : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
-                  (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
-                      ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR
-                      : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR);
+            // TODO: b/364981299 - Use partner config to allow user to customize text color.
+            if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+              if (id == primaryButtonId) {
+                updateTextColorForButton(
+                    button,
+                    enabled,
+                    enabled
+                        ? footerBarPrimaryButtonEnabledTextColor
+                        : footerBarPrimaryButtonDisabledTextColor);
+              } else if (id == secondaryButtonId) {
+                updateTextColorForButton(
+                    button,
+                    enabled,
+                    enabled
+                        ? footerBarSecondaryButtonEnabledTextColor
+                        : footerBarSecondaryButtonDisabledTextColor);
+              }
+            } else {
+              if (applyPartnerResources && !applyDynamicColor) {
+                updateButtonTextColorWithStates(
+                    button,
+                    (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
+                        ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
+                        : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
+                    (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
+                        ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR
+                        : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR);
+              }
             }
           }
         }
@@ -128,6 +159,10 @@
           if (button != null) {
             button.setVisibility(visibility);
             autoSetButtonBarVisibility();
+
+            if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+              setButtonWidthForExpressiveStyle(/* isDownButton= */ false);
+            }
           }
         }
       }
@@ -206,6 +241,20 @@
         a.getColor(R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterBackground, 0);
     footerButtonAlignEnd =
         a.getBoolean(R.styleable.SucFooterBarMixin_sucFooterBarButtonAlignEnd, false);
+    footerBarPrimaryButtonEnabledTextColor =
+        a.getColor(
+            R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButtonEnabledTextColor, 0);
+    footerBarSecondaryButtonEnabledTextColor =
+        a.getColor(
+            R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterButtonEnabledTextColor, 0);
+    footerBarPrimaryButtonDisabledTextColor =
+        a.getColor(
+            R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButtonDisabledTextColor, 0);
+    footerBarSecondaryButtonDisabledTextColor =
+        a.getColor(
+            R.styleable.SucFooterBarMixin_sucFooterBarSecondaryFooterButtonDisabledTextColor, 0);
+    footerBarButtonMiddleSpacing =
+        a.getDimensionPixelSize(R.styleable.SucFooterBarMixin_sucFooterBarButtonMiddleSpacing, 0);
 
     int primaryBtn =
         a.getResourceId(R.styleable.SucFooterBarMixin_sucFooterBarPrimaryFooterButton, 0);
@@ -379,15 +428,34 @@
   }
 
   /**
-   * Inflate FooterActionButton with layout "suc_button". Subclasses can implement this method to
+   * Inflate IFooterActionButton with layout "suc_button". Subclasses can implement this method to
    * modify the footer button layout as necessary.
    */
   @SuppressLint("InflateParams")
-  protected FooterActionButton createThemedButton(Context context, @StyleRes int theme) {
+  protected IFooterActionButton createThemedButton(Context context, @StyleRes int theme) {
+    if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+      try {
+        if (theme == R.style.SucGlifMaterialButton_Primary) {
+          return new MaterialFooterActionButton(
+              new ContextThemeWrapper(context, theme), null, R.attr.sucMaterialButtonStyle);
+        } else {
+          return new MaterialFooterActionButton(
+              new ContextThemeWrapper(context, theme), null, R.attr.sucMaterialOutlinedButtonStyle);
+        }
+      } catch (IllegalArgumentException e) {
+        LOG.e("Applyed invalid material theme: " + e);
+        // fallback theme style to glif theme
+        if (theme == R.style.SucGlifMaterialButton_Primary) {
+          theme = R.style.SucPartnerCustomizationButton_Primary;
+        } else {
+          theme = R.style.SucPartnerCustomizationButton_Secondary;
+        }
+      }
+    }
     // Inflate a single button from XML, which when using support lib, will take advantage of
     // the injected layout inflater and give us AppCompatButton instead.
     LayoutInflater inflater = LayoutInflater.from(new ContextThemeWrapper(context, theme));
-    return (FooterActionButton) inflater.inflate(R.layout.suc_button, null, false);
+    return (IFooterActionButton) inflater.inflate(R.layout.suc_button, null, false);
   }
 
   /** Sets primary button for footer. */
@@ -396,13 +464,21 @@
     ensureOnMainThread("setPrimaryButton");
     ensureFooterInflated();
 
+    int defaultPartnerTheme;
+    if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+      defaultPartnerTheme = R.style.SucGlifMaterialButton_Primary;
+    } else {
+      defaultPartnerTheme = R.style.SucPartnerCustomizationButton_Primary;
+    }
+
+    // TODO: b/364980746 - Use partner config to allow user to customize primary bg color.
     // Setup button partner config
     FooterButtonPartnerConfig footerButtonPartnerConfig =
         new FooterButtonPartnerConfig.Builder(footerButton)
             .setPartnerTheme(
                 getPartnerTheme(
                     footerButton,
-                    /* defaultPartnerTheme= */ R.style.SucPartnerCustomizationButton_Primary,
+                    /* defaultPartnerTheme= */ defaultPartnerTheme,
                     /* buttonBackgroundColorConfig= */ PartnerConfig
                         .CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR))
             .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
@@ -422,14 +498,32 @@
             .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
             .build();
 
-    FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
+    IFooterActionButton buttonImpl = inflateButton(footerButton, footerButtonPartnerConfig);
     // update information for primary button. Need to update as long as the button inflated.
+    Button button = (Button) buttonImpl;
     primaryButtonId = button.getId();
-    button.setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true);
+    if (buttonImpl instanceof MaterialFooterActionButton) {
+      ((MaterialFooterActionButton) buttonImpl)
+          .setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true);
+    } else if (button instanceof FooterActionButton) {
+      ((FooterActionButton) buttonImpl).setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true);
+    } else {
+      LOG.e("Set the primary button style error when setting primary button.");
+    }
     primaryButton = footerButton;
     primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig;
     onFooterButtonInflated(button, footerBarPrimaryBackgroundColor);
     onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig);
+    // TODO: b/364981299 - Use partner config to allow user to customize text color.
+    if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+      boolean enabled = primaryButton.isEnabled();
+      updateTextColorForButton(
+          button,
+          enabled,
+          enabled
+              ? footerBarPrimaryButtonEnabledTextColor
+              : footerBarPrimaryButtonDisabledTextColor);
+    }
     if (loggingObserver != null) {
       loggingObserver.log(
           new ButtonInflatedEvent(getPrimaryButtonView(), LoggingObserver.ButtonType.PRIMARY));
@@ -475,15 +569,26 @@
     isSecondaryButtonInPrimaryStyle = usePrimaryStyle;
     ensureFooterInflated();
 
+    int defaultPartnerTheme;
+    if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+      defaultPartnerTheme =
+          usePrimaryStyle
+              ? R.style.SucGlifMaterialButton_Primary
+              : R.style.SucGlifMaterialButton_Secondary;
+    } else {
+      defaultPartnerTheme =
+          usePrimaryStyle
+              ? R.style.SucPartnerCustomizationButton_Primary
+              : R.style.SucPartnerCustomizationButton_Secondary;
+    }
+
     // Setup button partner config
     FooterButtonPartnerConfig footerButtonPartnerConfig =
         new FooterButtonPartnerConfig.Builder(footerButton)
             .setPartnerTheme(
                 getPartnerTheme(
                     footerButton,
-                    /* defaultPartnerTheme= */ usePrimaryStyle
-                        ? R.style.SucPartnerCustomizationButton_Primary
-                        : R.style.SucPartnerCustomizationButton_Secondary,
+                    /* defaultPartnerTheme= */ defaultPartnerTheme,
                     /* buttonBackgroundColorConfig= */ usePrimaryStyle
                         ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR
                         : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR))
@@ -512,15 +617,32 @@
             .setTextStyleConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_TEXT_STYLE)
             .build();
 
-    FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
+    IFooterActionButton buttonImpl = inflateButton(footerButton, footerButtonPartnerConfig);
     // update information for secondary button. Need to update as long as the button inflated.
+    Button button = (Button) buttonImpl;
     secondaryButtonId = button.getId();
-    button.setPrimaryButtonStyle(usePrimaryStyle);
+    if (buttonImpl instanceof MaterialFooterActionButton) {
+      ((MaterialFooterActionButton) buttonImpl).setPrimaryButtonStyle(usePrimaryStyle);
+    } else if (button instanceof FooterActionButton) {
+      ((FooterActionButton) buttonImpl).setPrimaryButtonStyle(usePrimaryStyle);
+    } else {
+      LOG.e("Set the primary button style error when setting secondary button.");
+    }
     secondaryButton = footerButton;
     secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig;
 
     onFooterButtonInflated(button, footerBarSecondaryBackgroundColor);
     onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig);
+    // TODO: b/364981299 - Use partner config to allow user to customize text color.
+    if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+      boolean enabled = secondaryButton.isEnabled();
+      updateTextColorForButton(
+          button,
+          enabled,
+          enabled
+              ? footerBarSecondaryButtonEnabledTextColor
+              : footerBarSecondaryButtonDisabledTextColor);
+    }
     if (loggingObserver != null) {
       loggingObserver.log(new ButtonInflatedEvent(button, LoggingObserver.ButtonType.SECONDARY));
       footerButton.setLoggingObserver(loggingObserver);
@@ -545,7 +667,10 @@
     boolean isLandscape =
         context.getResources().getConfiguration().orientation
             == Configuration.ORIENTATION_LANDSCAPE;
-    if (isLandscape && isEvenlyWeightedButtons && isFooterButtonAlignedEnd()) {
+    if (isLandscape
+        && isEvenlyWeightedButtons
+        && isFooterButtonAlignedEnd()
+        && !PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
       addSpace();
     }
 
@@ -562,7 +687,7 @@
       }
       buttonContainer.addView(tempSecondaryButton);
     }
-    if (!isFooterButtonAlignedEnd()) {
+    if (!isFooterButtonAlignedEnd() && !PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
       addSpace();
     }
     if (tempPrimaryButton != null) {
@@ -570,6 +695,10 @@
     }
 
     setEvenlyWeightedButtons(tempPrimaryButton, tempSecondaryButton, isEvenlyWeightedButtons);
+
+    if (PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
+      setButtonWidthForExpressiveStyle(/* isDownButton= */ false);
+    }
   }
 
   private void setEvenlyWeightedButtons(
@@ -588,7 +717,7 @@
       if (primaryButton != null) {
         LinearLayout.LayoutParams primaryLayoutParams =
             (LinearLayout.LayoutParams) primaryButton.getLayoutParams();
-        if (null != primaryLayoutParams) {
+        if (primaryLayoutParams != null) {
           primaryLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
           primaryLayoutParams.weight = 0;
           primaryButton.setLayoutParams(primaryLayoutParams);
@@ -597,7 +726,7 @@
       if (secondaryButton != null) {
         LinearLayout.LayoutParams secondaryLayoutParams =
             (LinearLayout.LayoutParams) secondaryButton.getLayoutParams();
-        if (null != secondaryLayoutParams) {
+        if (secondaryLayoutParams != null) {
           secondaryLayoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
           secondaryLayoutParams.weight = 0;
           secondaryButton.setLayoutParams(secondaryLayoutParams);
@@ -606,6 +735,173 @@
     }
   }
 
+  // TODO: b/369285240 - Migrate setButtonWidthForExpressiveStyle of FooterBarMixin to
+  /** Sets button width for expressive style. */
+  public void setButtonWidthForExpressiveStyle(boolean isDownButton) {
+    final ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener =
+        new OnGlobalLayoutListener() {
+          @Override
+          public void onGlobalLayout() {
+            int initialLeftMargin = 0;
+            if (!isDownButton) {
+              Button primaryButton = getPrimaryButtonView();
+              Button secondaryButton = getSecondaryButtonView();
+              int footerBarWidth = buttonContainer.getWidth();
+              if (isTwoPaneLayout()) {
+                footerBarWidth = footerBarWidth / 2;
+                if (primaryButton != null) {
+                  // Set back the margin once down button scrolling to the bottom.
+                  LinearLayout.LayoutParams primaryLayoutParams =
+                      ((LayoutParams) primaryButton.getLayoutParams());
+                  if (primaryLayoutParams.leftMargin != initialLeftMargin) {
+                    primaryLayoutParams.leftMargin = initialLeftMargin;
+                    primaryButton.setLayoutParams(primaryLayoutParams);
+                  }
+                }
+                buttonContainer.setGravity(Gravity.END);
+              }
+
+              // TODO: b/364981820 - Use partner config to allow user to customize button width.
+              footerBarWidth =
+                  footerBarWidth
+                      - footerBarPaddingStart
+                      - footerBarPaddingEnd
+                      - footerBarButtonMiddleSpacing;
+
+              if (isBothButtons(primaryButton, secondaryButton)) {
+                LayoutParams primaryLayoutParams = (LayoutParams) primaryButton.getLayoutParams();
+                LayoutParams secondaryLayoutParams =
+                    (LayoutParams) secondaryButton.getLayoutParams();
+                if (primaryLayoutParams != null) {
+                  primaryLayoutParams.width = footerBarWidth / 2;
+                  primaryLayoutParams.setMarginStart(footerBarButtonMiddleSpacing / 2);
+                  primaryButton.setLayoutParams(primaryLayoutParams);
+                }
+                if (secondaryLayoutParams != null) {
+                  secondaryLayoutParams.width = footerBarWidth / 2;
+                  secondaryLayoutParams.setMarginEnd(footerBarButtonMiddleSpacing / 2);
+                  secondaryButton.setLayoutParams(secondaryLayoutParams);
+                }
+              } else if (isPrimaryButtonOnly(primaryButton, secondaryButton)) {
+                LayoutParams primaryLayoutParams = (LayoutParams) primaryButton.getLayoutParams();
+                if (primaryLayoutParams != null) {
+                  primaryLayoutParams.width = footerBarWidth;
+                  primaryButton.setLayoutParams(primaryLayoutParams);
+                }
+              } else if (isSecondaryOnly(primaryButton, secondaryButton)) {
+                LayoutParams secondaryLayoutParams =
+                    (LayoutParams) secondaryButton.getLayoutParams();
+                if (secondaryLayoutParams != null) {
+                  secondaryLayoutParams.width = footerBarWidth;
+                  secondaryButton.setLayoutParams(secondaryLayoutParams);
+                }
+              } else {
+                LOG.atInfo("There are no button visible in the footer bar.");
+              }
+            } else {
+              // TODO: b/364121308 - Extract values as attributes.
+              // Set up down button styles.
+              int width =
+                  context
+                      .getResources()
+                      .getDimensionPixelSize(R.dimen.suc_glif_expressive_down_button_width);
+              int height =
+                  context
+                      .getResources()
+                      .getDimensionPixelSize(R.dimen.suc_glif_expressive_down_button_height);
+              float radius =
+                  context
+                      .getResources()
+                      .getDimension(R.dimen.suc_glif_expressive_down_button_radius);
+              setSizeForButton(getPrimaryButtonView(), width, height);
+              setRadiusForButton(getPrimaryButtonView(), radius);
+
+              if (getPrimaryButton() != null && getSecondaryButton() == null) {
+                if (!isTwoPaneLayout()) {
+                  buttonContainer.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
+                } else {
+                  buttonContainer.setGravity(Gravity.NO_GRAVITY);
+                  int containerWidth = buttonContainer.getWidth();
+                  Button downButtonView = getPrimaryButtonView();
+                  LayoutParams primaryLayoutParams = (LayoutParams) downButtonView.getLayoutParams();
+                  int buttonWidth = primaryLayoutParams.width;
+                  int halfContainerWidth = containerWidth / 2;
+                  initialLeftMargin = primaryLayoutParams.leftMargin;
+                  primaryLayoutParams.leftMargin =
+                      (halfContainerWidth + (containerWidth / 2 - buttonWidth) / 2);
+                  downButtonView.setLayoutParams(primaryLayoutParams);
+                }
+              }
+            }
+            buttonContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+          }
+        };
+
+    buttonContainer.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
+  }
+
+  private boolean isTwoPaneLayout() {
+    return context.getResources().getBoolean(R.bool.sucTwoPaneLayoutStyle);
+  }
+
+  private boolean isBothButtons(Button primaryButton, Button secondaryButton) {
+    boolean isPrimaryVisible =
+        primaryButton != null && primaryButton.getVisibility() == View.VISIBLE;
+    boolean isSecondaryVisible =
+        secondaryButton != null && secondaryButton.getVisibility() == View.VISIBLE;
+    LOG.atDebug(
+        "isPrimaryVisible=" + isPrimaryVisible + ", isSecondaryVisible=" + isSecondaryVisible);
+    return isPrimaryVisible && isSecondaryVisible;
+  }
+
+  private boolean isPrimaryButtonOnly(Button primaryButton, Button secondaryButton) {
+    boolean isPrimaryOnly = primaryButton != null && secondaryButton == null;
+    boolean isPrimaryOnlyButSecondaryInvisible =
+        (primaryButton != null)
+            && (secondaryButton != null && secondaryButton.getVisibility() != View.VISIBLE);
+    LOG.atDebug(
+        "isPrimaryOnly="
+            + isPrimaryOnly
+            + ", isPrimaryOnlyButSecondaryInvisible="
+            + isPrimaryOnlyButSecondaryInvisible);
+    return isPrimaryOnly || isPrimaryOnlyButSecondaryInvisible;
+  }
+
+  private boolean isSecondaryOnly(Button primaryButton, Button secondaryButton) {
+    boolean isSecondaryOnly = secondaryButton != null && primaryButton == null;
+    boolean isSecondaryOnlyButPrimaryInvisible =
+        (secondaryButton != null)
+            && (primaryButton != null && primaryButton.getVisibility() != View.VISIBLE);
+    LOG.atDebug(
+        "isSecondaryOnly="
+            + isSecondaryOnly
+            + ", isSecondaryOnlyButPrimaryInvisible="
+            + isSecondaryOnlyButPrimaryInvisible);
+    return isSecondaryOnly || isSecondaryOnlyButPrimaryInvisible;
+  }
+
+  private void setSizeForButton(Button button, int width, int height) {
+    if (button != null) {
+      LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) button.getLayoutParams();
+      layoutParams.width = width;
+      layoutParams.height = height;
+      button.setLayoutParams(layoutParams);
+    }
+  }
+
+  private void setRadiusForButton(Button button, float radius) {
+    if (button != null) {
+      if (button instanceof MaterialButton) {
+        ((MaterialButton) button).setCornerRadius((int) radius);
+      } else {
+        GradientDrawable gradientDrawable = FooterButtonStyleUtils.getGradientDrawable(button);
+        if (gradientDrawable != null) {
+          gradientDrawable.setCornerRadius(radius);
+        }
+      }
+    }
+  }
+
   /**
    * Notifies that the footer button has been inInflated and add to the view hierarchy. Calling
    * super is necessary while subclass implement it.
@@ -631,7 +927,9 @@
     int overrideTheme = footerButton.getTheme();
 
     // Set the default theme if theme is not set, or when running in setup flow.
-    if (footerButton.getTheme() == 0 || applyPartnerResources) {
+    if (footerButton.getTheme() == 0
+        || applyPartnerResources
+        || PartnerConfigHelper.isGlifExpressiveEnabled(context)) {
       overrideTheme = defaultPartnerTheme;
     }
     // TODO: Make sure customize attributes in theme can be applied during setup flow.
@@ -640,9 +938,15 @@
     if (applyPartnerResources) {
       int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig);
       if (color == Color.TRANSPARENT) {
-        overrideTheme = R.style.SucPartnerCustomizationButton_Secondary;
+        overrideTheme =
+            PartnerConfigHelper.isGlifExpressiveEnabled(context)
+                ? R.style.SucGlifMaterialButton_Secondary
+                : R.style.SucPartnerCustomizationButton_Secondary;
       } else {
-        overrideTheme = R.style.SucPartnerCustomizationButton_Primary;
+        overrideTheme =
+            PartnerConfigHelper.isGlifExpressiveEnabled(context)
+                ? R.style.SucGlifMaterialButton_Primary
+                : R.style.SucPartnerCustomizationButton_Primary;
       }
     }
     return overrideTheme;
@@ -710,10 +1014,11 @@
         && getSecondaryButtonView().getVisibility() == View.VISIBLE;
   }
 
-  private FooterActionButton inflateButton(
+  private IFooterActionButton inflateButton(
       FooterButton footerButton, FooterButtonPartnerConfig footerButtonPartnerConfig) {
-    FooterActionButton button =
+    IFooterActionButton buttonImpl =
         createThemedButton(context, footerButtonPartnerConfig.getPartnerTheme());
+    Button button = (Button) buttonImpl;
     button.setId(View.generateViewId());
 
     // apply initial configuration into button view.
@@ -721,10 +1026,15 @@
     button.setOnClickListener(footerButton);
     button.setVisibility(footerButton.getVisibility());
     button.setEnabled(footerButton.isEnabled());
-    button.setFooterButton(footerButton);
-
+    if (buttonImpl instanceof MaterialFooterActionButton) {
+      ((MaterialFooterActionButton) buttonImpl).setFooterButton(footerButton);
+    } else if (button instanceof FooterActionButton) {
+      ((FooterActionButton) buttonImpl).setFooterButton(footerButton);
+    } else {
+      LOG.e("Set the footer button error!");
+    }
     footerButton.setOnButtonEventListener(createButtonEventListener(button.getId()));
-    return button;
+    return buttonImpl;
   }
 
   // TODO: Make sure customize attributes in theme can be applied during setup flow.
@@ -850,4 +1160,12 @@
   public PersistableBundle getLoggingMetrics() {
     return metrics.getMetrics();
   }
+
+  private void updateTextColorForButton(Button button, boolean enable, int color) {
+    if (enable) {
+      FooterButtonStyleUtils.updateButtonTextEnabledColor(button, color);
+    } else {
+      FooterButtonStyleUtils.updateButtonTextDisabledColor(button, color);
+    }
+  }
 }
diff --git a/main/java/com/google/android/setupcompat/template/FooterButton.java b/main/java/com/google/android/setupcompat/template/FooterButton.java
index 38b81c2..33bf265 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButton.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButton.java
@@ -80,6 +80,7 @@
    * @param listener The listener for button.
    * @param buttonType The type of button.
    * @param theme The theme for button.
+   * @param visibility the visibility for button.
    */
   private FooterButton(
       CharSequence text,
@@ -87,13 +88,15 @@
       @ButtonType int buttonType,
       @StyleRes int theme,
       Locale locale,
-      int direction) {
+      int direction,
+      int visibility) {
     this.text = text;
     onClickListener = listener;
     this.buttonType = buttonType;
     this.theme = theme;
     this.locale = locale;
     this.direction = direction;
+    this.visibility = visibility;
   }
 
   /** Returns the text that this footer button is displaying. */
@@ -366,6 +369,7 @@
    *         .setTheme(R.style.SuwGlifButton_Primary)
    *         .setTextLocale(Locale.CANADA)
    *         .setLayoutDirection(View.LAYOUT_DIRECTION_LTR)
+   *         .setVisibility(View.VISIBLE)
    *         .build();
    * </pre>
    */
@@ -378,6 +382,8 @@
     @ButtonType private int buttonType = ButtonType.OTHER;
     private int theme = 0;
 
+    private int visibility = View.VISIBLE;
+
     public Builder(@NonNull Context context) {
       this.context = context;
     }
@@ -424,8 +430,15 @@
       return this;
     }
 
+    /** Sets the {@code visibility} of FooterButton. */
+    public Builder setVisibility(int visibility) {
+      this.visibility = visibility;
+      return this;
+    }
+
     public FooterButton build() {
-      return new FooterButton(text, onClickListener, buttonType, theme, locale, direction);
+      return new FooterButton(
+          text, onClickListener, buttonType, theme, locale, direction, visibility);
     }
   }
 }
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
index fc56aad..3045002 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -291,12 +291,12 @@
       }
       float alpha =
           PartnerConfigHelper.get(context).getFraction(context, buttonRippleColorAlphaConfig);
-      updateButtonRippleColor(button, textDefaultColor, alpha);
+      updateButtonRippleColor(context, button, textDefaultColor, alpha);
     }
   }
 
   private static void updateButtonRippleColor(
-      Button button, @ColorInt int textColor, float rippleAlpha) {
+      Context context, 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.
@@ -315,7 +315,13 @@
           new ColorStateList(
               new int[][] {pressedState, focusState, StateSet.NOTHING},
               new int[] {argbColor, argbColor, Color.TRANSPARENT});
-      rippleDrawable.setColor(colorStateList);
+      if (PartnerConfigHelper.isGlifExpressiveEnabled(context)
+          && button instanceof MaterialFooterActionButton) {
+        MaterialFooterActionButton materialButton = (MaterialFooterActionButton) button;
+        materialButton.setRippleColor(colorStateList);
+      } else {
+        rippleDrawable.setColor(colorStateList);
+      }
     }
   }
 
@@ -388,9 +394,15 @@
       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);
+      if (PartnerConfigHelper.isGlifExpressiveEnabled(context)
+          && button instanceof MaterialFooterActionButton) {
+        MaterialFooterActionButton materialButton = (MaterialFooterActionButton) button;
+        materialButton.setCornerRadius((int) radius);
+      } else {
+        GradientDrawable gradientDrawable = getGradientDrawable(button);
+        if (gradientDrawable != null) {
+          gradientDrawable.setCornerRadius(radius);
+        }
       }
     }
   }
diff --git a/main/java/com/google/android/setupcompat/template/IFooterActionButton.java b/main/java/com/google/android/setupcompat/template/IFooterActionButton.java
new file mode 100644
index 0000000..59f7bed
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/IFooterActionButton.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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.view.MotionEvent;
+
+/**
+ * Interface for footer action buttons in Setup library to indicate Android Button or Material
+ * button classes.
+ *
+ * <p>This interface defines common methods for footer action buttons, regardless of their specific
+ * implementation. It provides a way to interact with footer buttons and determine their style
+ * attributes.
+ */
+public interface IFooterActionButton {
+
+  /**
+   * Handles touch events for the footer action button, ensuring accessibility and proper behavior
+   * even when the button is disabled.
+   *
+   * @param event The MotionEvent object representing the touch event.
+   * @return true if the event was consumed by the button, false otherwise.
+   */
+  boolean onTouchEvent(MotionEvent event);
+
+  /** Returns true when the footer button is primary button style. */
+  boolean isPrimaryButtonStyle();
+}
diff --git a/main/java/com/google/android/setupcompat/template/MaterialFooterActionButton.java b/main/java/com/google/android/setupcompat/template/MaterialFooterActionButton.java
new file mode 100644
index 0000000..da86746
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/template/MaterialFooterActionButton.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 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.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.material.button.MaterialButton;
+
+/** Material Button that can react to touch when disabled. */
+public class MaterialFooterActionButton extends MaterialButton implements IFooterActionButton {
+  @VisibleForTesting FooterButton footerButton;
+  private boolean isPrimaryButtonStyle = false;
+
+  public MaterialFooterActionButton(Context context, AttributeSet attrs) {
+    super(context, attrs);
+  }
+
+  public MaterialFooterActionButton(Context context, AttributeSet attrs, int value) {
+    super(context, attrs, value);
+  }
+
+  void setFooterButton(FooterButton footerButton) {
+    this.footerButton = footerButton;
+  }
+
+  @SuppressLint("ClickableViewAccessibility")
+  @Override
+  public boolean onTouchEvent(MotionEvent event) {
+    if (event.getAction() == MotionEvent.ACTION_DOWN) {
+      if (footerButton != null
+          && !footerButton.isEnabled()
+          && footerButton.getVisibility() == View.VISIBLE) {
+        // getOnClickListenerWhenDisabled is responsible for handling accessibility correctly,
+        // calling performClick if necessary.
+        OnClickListener listener = footerButton.getOnClickListenerWhenDisabled();
+        if (listener != null) {
+          listener.onClick(this);
+        }
+      }
+    }
+    return super.onTouchEvent(event);
+  }
+
+  /**
+   * Sets this footer button is primary button style.
+   *
+   * @param isPrimaryButtonStyle True if this button is primary button style.
+   */
+  void setPrimaryButtonStyle(boolean isPrimaryButtonStyle) {
+    this.isPrimaryButtonStyle = isPrimaryButtonStyle;
+  }
+
+  @Override
+  public boolean isPrimaryButtonStyle() {
+    return isPrimaryButtonStyle;
+  }
+}
diff --git a/main/java/com/google/android/setupcompat/util/KeyboardHelper.java b/main/java/com/google/android/setupcompat/util/KeyboardHelper.java
new file mode 100644
index 0000000..4989f5d
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/util/KeyboardHelper.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 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.content.Context;
+import androidx.annotation.NonNull;
+import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+
+/** Helper class to handle keyboard related operations. */
+public final class KeyboardHelper {
+
+  /** Returns whether the keyboard focus changed is enabled. */
+  public static boolean isKeyboardFocusEnhancementEnabled(@NonNull Context context) {
+    return PartnerConfigHelper.isKeyboardFocusEnhancementEnabled(context);
+  }
+
+  private KeyboardHelper() {}
+}
diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
index 9c73ea9..2b0aedf 100644
--- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
+++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
@@ -22,7 +22,6 @@
 import android.os.Build;
 import android.provider.Settings;
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 import com.google.errorprone.annotations.InlineMe;
 import java.util.Arrays;
 
@@ -52,10 +51,12 @@
   /** Extra for notifying an Activity that what SetupWizard flow is. */
   public static final String EXTRA_SUW_LIFECYCLE = "suw_lifecycle";
 
-  @VisibleForTesting public static final String ACTION_NEXT = "com.android.wizard.NEXT";
+  public static final String ACTION_NEXT = "com.android.wizard.NEXT";
 
   public static final String EXTRA_WIZARD_BUNDLE = "wizardBundle";
-  private static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
+
+  /** Extra used for including the resultcode of a wizardmanager action. */
+  public static final String EXTRA_RESULT_CODE = "com.android.setupwizard.ResultCode";
 
   /** Extra for notifying an Activity that it is inside the first SetupWizard flow or not. */
   public static final String EXTRA_IS_FIRST_RUN = "firstRun";