DO NOT MERGE - Merge Android 13

Bug: 242648940
Merged-In: I84ad65b397ac38f30f2a4a8ad8ba384fbb50abdb
Change-Id: I4d09c5d6c64a3030e70ce591f1ab10f83bba0f02
diff --git a/Android.bp b/Android.bp
index 01e654d..d3a42a7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -80,6 +80,7 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
+        "error_prone_annotations",
     ],
     min_sdk_version: "14",
     sdk_version: "current",
diff --git a/exempting_lint_checks.txt b/exempting_lint_checks.txt
index 90b8caf..4129b20 100644
--- a/exempting_lint_checks.txt
+++ b/exempting_lint_checks.txt
@@ -5,3 +5,13 @@
 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) {
+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/PartnerCustomizationLayout.java: ObsoleteSdkInt: @TargetApi(VERSION_CODES.HONEYCOMB)
+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/internal/TemplateLayout.java: ObsoleteSdkInt: @TargetApi(VERSION_CODES.HONEYCOMB)
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/logging/internal/MetricBundleConverter.java: NewApi: bundle.putParcelable(MetricBundleKeys.CUSTOM_EVENT_BUNDLE, CustomEvent.toBundle(customEvent));
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterBarMixin.java: NewApi: onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig);
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java: NewApi: FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java: NewApi: FooterButtonStyleUtils.updateButtonRippleColorWithPartnerConfig(
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/util/SystemBarHelper.java: AnnotateVersionCheck: public static void setImeInsetView(final View view) {
+third_party/java_src/android_libs/setupcompat/main/java/com/google/android/setupcompat/view/StatusBarBackgroundLayout.java: ObsoleteSdkInt: @TargetApi(VERSION_CODES.HONEYCOMB)
diff --git a/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl b/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
index 09f7c9a..e8cb7e5 100644
--- a/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
+++ b/main/aidl/com/google/android/setupcompat/ISetupCompatService.aidl
@@ -26,4 +26,6 @@
   oneway void validateActivity(String screenName, in Bundle arguments) = 0;
 
   oneway void logMetric(int metricType, in Bundle arguments, in Bundle extras) = 1;
+
+  oneway void onFocusStatusChanged(in Bundle bundle) = 2;
 }
diff --git a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
index e5ba0c5..37cc358 100644
--- a/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
+++ b/main/java/com/google/android/setupcompat/PartnerCustomizationLayout.java
@@ -29,9 +29,13 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.setupcompat.internal.FocusChangedMetricHelper;
 import com.google.android.setupcompat.internal.LifecycleFragment;
 import com.google.android.setupcompat.internal.PersistableBundles;
+import com.google.android.setupcompat.internal.SetupCompatServiceInvoker;
 import com.google.android.setupcompat.internal.TemplateLayout;
 import com.google.android.setupcompat.logging.CustomEvent;
 import com.google.android.setupcompat.logging.MetricKey;
@@ -44,6 +48,7 @@
 import com.google.android.setupcompat.util.BuildCompatUtils;
 import com.google.android.setupcompat.util.Logger;
 import com.google.android.setupcompat.util.WizardManagerHelper;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 
 /** A templatization layout with consistent style used in Setup Wizard or app itself. */
 public class PartnerCustomizationLayout extends TemplateLayout {
@@ -71,24 +76,33 @@
 
   private Activity activity;
 
+  @CanIgnoreReturnValue
   public PartnerCustomizationLayout(Context context) {
     this(context, 0, 0);
   }
 
+  @CanIgnoreReturnValue
   public PartnerCustomizationLayout(Context context, int template) {
     this(context, template, 0);
   }
 
+  @CanIgnoreReturnValue
   public PartnerCustomizationLayout(Context context, int template, int containerId) {
     super(context, template, containerId);
     init(null, R.attr.sucLayoutTheme);
   }
 
+  @VisibleForTesting
+  final ViewTreeObserver.OnWindowFocusChangeListener windowFocusChangeListener =
+      this::onFocusChanged;
+
+  @CanIgnoreReturnValue
   public PartnerCustomizationLayout(Context context, AttributeSet attrs) {
     super(context, attrs);
     init(attrs, R.attr.sucLayoutTheme);
   }
 
+  @CanIgnoreReturnValue
   @TargetApi(VERSION_CODES.HONEYCOMB)
   public PartnerCustomizationLayout(Context context, AttributeSet attrs, int defStyleAttr) {
     super(context, attrs, defStyleAttr);
@@ -203,14 +217,17 @@
   protected void onAttachedToWindow() {
     super.onAttachedToWindow();
     LifecycleFragment.attachNow(activity);
+    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2
+        && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
+      getViewTreeObserver().addOnWindowFocusChangeListener(windowFocusChangeListener);
+    }
     getMixin(FooterBarMixin.class).onAttachedToWindow();
   }
 
   @Override
   protected void onDetachedFromWindow() {
     super.onDetachedFromWindow();
-    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP
-        && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+    if (VERSION.SDK_INT >= Build.VERSION_CODES.Q
         && WizardManagerHelper.isAnySetupWizard(activity.getIntent())) {
       FooterBarMixin footerBarMixin = getMixin(FooterBarMixin.class);
       footerBarMixin.onDetachedFromWindow();
@@ -233,6 +250,10 @@
           getContext(),
           CustomEvent.create(MetricKey.get("SetupCompatMetrics", activity), persistableBundle));
     }
+
+    if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR2) {
+      getViewTreeObserver().removeOnWindowFocusChangeListener(windowFocusChangeListener);
+    }
   }
 
   public static Activity lookupActivityFromContext(Context context) {
@@ -297,4 +318,16 @@
   public boolean useFullDynamicColor() {
     return shouldApplyDynamicColor() && useFullDynamicColorAttr;
   }
+
+  /**
+   * Invoke the method onFocusStatusChanged when onWindowFocusChangeListener receive onFocusChanged.
+   */
+  private void onFocusChanged(boolean hasFocus) {
+    SetupCompatServiceInvoker.get(getContext())
+        .onFocusStatusChanged(
+            FocusChangedMetricHelper.getScreenName(activity),
+            FocusChangedMetricHelper.getExtraBundle(
+                activity, PartnerCustomizationLayout.this, hasFocus));
+  }
 }
+
diff --git a/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java b/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
index 3c707ae..28ced66 100644
--- a/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/ExecutorProvider.java
@@ -33,7 +33,6 @@
 public final class ExecutorProvider<T extends Executor> {
 
   private static final int SETUP_METRICS_LOGGER_MAX_QUEUED = 50;
-  private static final int SETUP_COMPAT_BINDBACK_MAX_QUEUED = 1;
   /**
    * Creates a single threaded {@link ExecutorService} with a maximum pool size {@code maxSize}.
    * Jobs submitted when the pool is full causes {@link
@@ -43,11 +42,6 @@
       new ExecutorProvider<>(
           createSizeBoundedExecutor("SetupCompatServiceInvoker", SETUP_METRICS_LOGGER_MAX_QUEUED));
 
-  public static final ExecutorProvider<ExecutorService> setupCompatExecutor =
-      new ExecutorProvider<>(
-          createSizeBoundedExecutor(
-              "SetupBindbackServiceExecutor", SETUP_COMPAT_BINDBACK_MAX_QUEUED));
-
   private final T executor;
 
   @Nullable private T injectedExecutor;
diff --git a/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java b/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java
new file mode 100644
index 0000000..39ade19
--- /dev/null
+++ b/main/java/com/google/android/setupcompat/internal/FocusChangedMetricHelper.java
@@ -0,0 +1,88 @@
+/*
+ * 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.internal;
+
+import android.app.Activity;
+import android.os.Bundle;
+import androidx.annotation.StringDef;
+import com.google.android.setupcompat.internal.FocusChangedMetricHelper.Constants.ExtraKey;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A help class response to generate extra bundle and capture screen name for interruption metric.
+ */
+public class FocusChangedMetricHelper {
+  private FocusChangedMetricHelper() {}
+
+  public static final String getScreenName(Activity activity) {
+    return activity.getComponentName().toShortString();
+  }
+
+  public static final Bundle getExtraBundle(
+      Activity activity, TemplateLayout layout, boolean hasFocus) {
+    Bundle bundle = new Bundle();
+
+    bundle.putString(ExtraKey.PACKAGE_NAME, activity.getComponentName().getPackageName());
+    bundle.putString(ExtraKey.SCREEN_NAME, activity.getComponentName().getShortClassName());
+    bundle.putInt(ExtraKey.HASH_CODE, layout.hashCode());
+    bundle.putBoolean(ExtraKey.HAS_FOCUS, hasFocus);
+    bundle.putLong(ExtraKey.TIME_IN_MILLIS, System.currentTimeMillis());
+
+    return bundle;
+  }
+
+  /**
+   * Constant values used by {@link
+   * com.google.android.setupcompat.internal.FocusChangedMetricHelper}.
+   */
+  public static final class Constants {
+    @Retention(RetentionPolicy.SOURCE)
+    @StringDef({
+      ExtraKey.PACKAGE_NAME,
+      ExtraKey.SCREEN_NAME,
+      ExtraKey.HASH_CODE,
+      ExtraKey.HAS_FOCUS,
+      ExtraKey.TIME_IN_MILLIS
+    })
+    public @interface ExtraKey {
+
+      /** This key will be used to save the package name. */
+      String PACKAGE_NAME = "packageName";
+
+      /** This key will be used to save the activity name. */
+      String SCREEN_NAME = "screenName";
+
+      /**
+       * This key will be used to save the has code of {@link
+       * com.google.android.setupcompat.PartnerCustomizationLayout}.
+       */
+      String HASH_CODE = "hash";
+
+      /**
+       * This key will be used to save whether the window which is including the {@link
+       * com.google.android.setupcompat.PartnerCustomizationLayout}. has focus or not.
+       */
+      String HAS_FOCUS = "focus";
+
+      /** This key will be use to save the time stamp in milliseconds. */
+      String TIME_IN_MILLIS = "timeInMillis";
+    }
+
+    private Constants() {}
+  }
+}
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
index 149da54..ed9c0e3 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceInvoker.java
@@ -54,12 +54,23 @@
 
   public void bindBack(String screenName, Bundle bundle) {
     try {
-      setupCompatExecutor.execute(() -> invokeBindBack(screenName, bundle));
+      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}.
+   */
+  public void onFocusStatusChanged(String screenName, Bundle bundle) {
+    try {
+      loggingExecutor.execute(() -> invokeOnWindowFocusChanged(screenName, bundle));
+    } catch (RejectedExecutionException e) {
+      LOG.e(String.format("Screen %s report focus changed failed.", screenName), e);
+    }
+  }
+
   private void invokeLogMetric(
       @MetricType int metricType, @SuppressWarnings("unused") Bundle args) {
     try {
@@ -76,6 +87,29 @@
     }
   }
 
+  private void invokeOnWindowFocusChanged(String screenName, Bundle bundle) {
+    try {
+      ISetupCompatService setupCompatService =
+          SetupCompatServiceProvider.get(
+              context, waitTimeInMillisForServiceConnection, TimeUnit.MILLISECONDS);
+      if (setupCompatService != null) {
+        setupCompatService.onFocusStatusChanged(bundle);
+      } else {
+        LOG.w(
+            "Report focusChange failed since service reference is null. Are the permission valid?");
+      }
+    } catch (InterruptedException
+        | TimeoutException
+        | RemoteException
+        | UnsupportedOperationException e) {
+      LOG.e(
+          String.format(
+              "Exception occurred while %s trying report windowFocusChange to SetupWizard.",
+              screenName),
+          e);
+    }
+  }
+
   private void invokeBindBack(String screenName, Bundle bundle) {
     try {
       ISetupCompatService setupCompatService =
@@ -96,14 +130,12 @@
   private SetupCompatServiceInvoker(Context context) {
     this.context = context;
     this.loggingExecutor = ExecutorProvider.setupCompatServiceInvoker.get();
-    this.setupCompatExecutor = ExecutorProvider.setupCompatExecutor.get();
     this.waitTimeInMillisForServiceConnection = MAX_WAIT_TIME_FOR_CONNECTION_MS;
   }
 
   private final Context context;
 
   private final ExecutorService loggingExecutor;
-  private final ExecutorService setupCompatExecutor;
   private final long waitTimeInMillisForServiceConnection;
 
   public static synchronized SetupCompatServiceInvoker get(Context context) {
diff --git a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
index e75d991..e259f22 100644
--- a/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
+++ b/main/java/com/google/android/setupcompat/internal/SetupCompatServiceProvider.java
@@ -82,8 +82,8 @@
         return waitForConnection(timeout, timeUnit);
 
       case NOT_STARTED:
-        throw new IllegalStateException(
-            "NOT_STARTED state only possible before instance is created.");
+        LOG.w("NOT_STARTED state only possible before instance is created.");
+        return null;
     }
     throw new IllegalStateException("Unknown state = " + serviceContext.state);
   }
@@ -172,7 +172,8 @@
     return serviceContext;
   }
 
-  private void swapServiceContextAndNotify(ServiceContext latestServiceContext) {
+  @VisibleForTesting
+  void swapServiceContextAndNotify(ServiceContext latestServiceContext) {
     LOG.atInfo(
         String.format("State changed: %s -> %s", serviceContext.state, latestServiceContext.state));
 
@@ -200,7 +201,7 @@
         return countDownLatch;
       }
       countDownLatch = createCountDownLatch();
-    } while (!connectedConditionRef.compareAndSet(/* expect= */ null, countDownLatch));
+    } while (!connectedConditionRef.compareAndSet(/* expectedValue= */ null, countDownLatch));
     return countDownLatch;
   }
 
@@ -285,7 +286,8 @@
     REBIND_REQUIRED
   }
 
-  private static final class ServiceContext {
+  @VisibleForTesting
+  static final class ServiceContext {
     final State state;
     @Nullable final ISetupCompatService compatService;
 
@@ -298,7 +300,8 @@
       }
     }
 
-    private ServiceContext(State state) {
+    @VisibleForTesting
+    ServiceContext(State state) {
       this(state, /* compatService= */ null);
     }
   }
diff --git a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java b/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
deleted file mode 100644
index 2aa1240..0000000
--- a/main/java/com/google/android/setupcompat/logging/internal/PartnerCustomizedResourceListMetric.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.
- */
-
-package com.google.android.setupcompat.logging.internal;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
-import android.os.PersistableBundle;
-import androidx.annotation.VisibleForTesting;
-import com.google.android.setupcompat.logging.CustomEvent;
-import com.google.android.setupcompat.logging.MetricKey;
-import com.google.android.setupcompat.logging.SetupMetricsLogger;
-
-/** Uses to log internal event customization resource list. */
-@TargetApi(VERSION_CODES.Q)
-public class PartnerCustomizedResourceListMetric {
-
-  public static void logMetrics(Context context, String deviceDisplayName, Bundle bundle) {
-    PersistableBundle logBundle =
-        buildLogBundleFromResourceConfigBundle(context.getPackageName(), deviceDisplayName, bundle);
-    if (!logBundle.isEmpty()) {
-      SetupMetricsLogger.logCustomEvent(
-          context,
-          CustomEvent.create(
-              MetricKey.get("PartnerCustomizationResource", "NoScreenName"), logBundle));
-    }
-  }
-
-  @VisibleForTesting
-  public static PersistableBundle buildLogBundleFromResourceConfigBundle(
-      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);
-      }
-    }
-
-    return persistableBundle;
-  }
-}
diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index 6d92c40..4d2a0c9 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -17,6 +17,7 @@
 package com.google.android.setupcompat.template;
 
 import static com.google.android.setupcompat.internal.Preconditions.ensureOnMainThread;
+import static java.lang.Math.max;
 
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
@@ -73,7 +74,7 @@
   @VisibleForTesting final boolean applyDynamicColor;
   @VisibleForTesting final boolean useFullDynamicColor;
 
-  @VisibleForTesting LinearLayout buttonContainer;
+  @VisibleForTesting public LinearLayout buttonContainer;
   private FooterButton primaryButton;
   private FooterButton secondaryButton;
   @IdRes private int primaryButtonId;
@@ -226,7 +227,7 @@
     FooterButtonStyleUtils.clearSavedDefaultTextColor();
   }
 
-  private boolean isFooterButtonAlignedEnd() {
+  protected boolean isFooterButtonAlignedEnd() {
     if (PartnerConfigHelper.get(context)
         .isPartnerConfigAvailable(PartnerConfig.CONFIG_FOOTER_BUTTON_ALIGNED_END)) {
       return PartnerConfigHelper.get(context)
@@ -240,18 +241,16 @@
     if (!isSecondaryButtonInPrimaryStyle) {
       return false;
     }
-    // TODO: Support neutral button style in glif layout for phone and tablet
     PartnerConfigHelper.get(context);
-    return context.getResources().getConfiguration().smallestScreenWidthDp >= 600
-        && PartnerConfigHelper.isNeutralButtonStyleEnabled(context);
+    return PartnerConfigHelper.isNeutralButtonStyleEnabled(context);
   }
 
   private View addSpace() {
-    LinearLayout buttonContainer = ensureFooterInflated();
+    LinearLayout buttonContainerlayout = ensureFooterInflated();
     View space = new View(context);
     space.setLayoutParams(new LayoutParams(0, 0, 1.0f));
     space.setVisibility(View.INVISIBLE);
-    buttonContainer.addView(space);
+    buttonContainerlayout.addView(space);
     return space;
   }
 
@@ -531,8 +530,7 @@
       }
       buttonContainer.addView(tempSecondaryButton);
     }
-    if (!isFooterButtonAlignedEnd()
-        && (!isEvenlyWeightedButtons || (isEvenlyWeightedButtons && isLandscape))) {
+    if (!isFooterButtonAlignedEnd()) {
       addSpace();
     }
     if (tempPrimaryButton != null) {
@@ -545,21 +543,15 @@
   private void setEvenlyWeightedButtons(
       Button primaryButton, Button secondaryButton, boolean isEvenlyWeighted) {
     if (primaryButton != null && secondaryButton != null && isEvenlyWeighted) {
-      LinearLayout.LayoutParams primaryLayoutParams =
-          (LinearLayout.LayoutParams) primaryButton.getLayoutParams();
-      if (null != primaryLayoutParams) {
-        primaryLayoutParams.width = 0;
-        primaryLayoutParams.weight = 1.0f;
-        primaryButton.setLayoutParams(primaryLayoutParams);
-      }
+      primaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
+      int primaryButtonMeasuredWidth = primaryButton.getMeasuredWidth();
+      secondaryButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
 
-      LinearLayout.LayoutParams secondaryLayoutParams =
-          (LinearLayout.LayoutParams) secondaryButton.getLayoutParams();
-      if (null != secondaryLayoutParams) {
-        secondaryLayoutParams.width = 0;
-        secondaryLayoutParams.weight = 1.0f;
-        secondaryButton.setLayoutParams(secondaryLayoutParams);
-      }
+      int secondaryButtonMeasuredWidth = secondaryButton.getMeasuredWidth();
+      int maxButtonMeasureWidth = max(primaryButtonMeasuredWidth, secondaryButtonMeasuredWidth);
+
+      primaryButton.getLayoutParams().width = maxButtonMeasureWidth;
+      secondaryButton.getLayoutParams().width = maxButtonMeasureWidth;
     } else {
       if (primaryButton != null) {
         LinearLayout.LayoutParams primaryLayoutParams =
@@ -615,10 +607,7 @@
       int color = PartnerConfigHelper.get(context).getColor(context, buttonBackgroundColorConfig);
       if (color == Color.TRANSPARENT) {
         overrideTheme = R.style.SucPartnerCustomizationButton_Secondary;
-      } else if (color != Color.TRANSPARENT) {
-        // TODO: remove the constrain (color != Color.WHITE), need to check all pages
-        // go well without customization. It should be fine since the default value of secondary bg
-        // color is set as transparent.
+      } else {
         overrideTheme = R.style.SucPartnerCustomizationButton_Primary;
       }
     }
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
index ef2aa6b..476d45b 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -35,7 +35,6 @@
 import android.view.ViewGroup;
 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;
@@ -429,7 +428,7 @@
     defaultTextColor.clear();
   }
 
-  @VisibleForTesting
+  /** Gets {@code GradientDrawable} from given {@code button}. */
   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
diff --git a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
index 5b7c3ad..3c4e2a2 100644
--- a/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
+++ b/main/java/com/google/android/setupcompat/util/BuildCompatUtils.java
@@ -60,26 +60,26 @@
    * <p>Supported configurations:
    *
    * <ul>
-   *   <li>For current Android release: while new API is not finalized yet (CODENAME = "T", SDK_INT
-   *       = 33)
+   *   <li>For current Android release: while new API is not finalized yet (CODENAME = "Tiramisu",
+   *       SDK_INT = 33)
    *   <li>For current Android release: when new API is finalized (CODENAME = "REL", SDK_INT = 32)
    *   <li>For next Android release (CODENAME = "U", SDK_INT = 34+)
    * </ul>
    *
    * <p>Note that Build.VERSION_CODES.T cannot be used here until final SDK is available in all
-   * channels, because it is equal to Build.VERSION_CODES.CUR_DEVELOPMENT before API
-   * finalization.
+   * 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 T.
    */
   public static boolean isAtLeastT() {
-    if (!isAtLeastS()) {
-      return false;
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+      return true;
     }
     return (Build.VERSION.CODENAME.equals("REL") && Build.VERSION.SDK_INT >= 33)
         || (Build.VERSION.CODENAME.length() == 1
             && Build.VERSION.CODENAME.charAt(0) >= 'T'
-            && Build.VERSION.CODENAME.charAt(0) <= 'Z');
+            && Build.VERSION.CODENAME.charAt(0) <= 'Z')
+        || (Build.VERSION.CODENAME.equals("Tiramisu") && Build.VERSION.SDK_INT >= 32);
   }
 
   private BuildCompatUtils() {}
diff --git a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
index 84bd68b..90de25e 100644
--- a/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
+++ b/main/java/com/google/android/setupcompat/util/WizardManagerHelper.java
@@ -186,6 +186,17 @@
   }
 
   /**
+   * Checks whether an intent is running in the portal setup wizard flow.
+   *
+   * @param originalIntent The original intent that was used to start the step, usually via {@link
+   *     Activity#getIntent()}.
+   * @return true if the intent passed in was running in portal setup wizard.
+   */
+  public static boolean isPortalSetupWizard(Intent originalIntent) {
+    return originalIntent != null && originalIntent.getBooleanExtra(EXTRA_IS_PORTAL_SETUP, false);
+  }
+
+  /**
    * Checks whether an intent is running in the deferred setup wizard flow.
    *
    * @param originalIntent The original intent that was used to start the step, usually via {@link
diff --git a/main/res/values/styles.xml b/main/res/values/styles.xml
index 6474426..6625e83 100644
--- a/main/res/values/styles.xml
+++ b/main/res/values/styles.xml
@@ -45,13 +45,13 @@
         <item name="android:theme">@style/SucPartnerCustomizationButton.Primary</item>
 
         <!-- Values used in styles -->
-        <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
+        <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/sucFooterBarButtonFontFamily</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>
+        <item name="android:stateListAnimator" tools:ignore="NewApi">@null</item>
 
         <!-- Values used in themes -->
         <item name="android:buttonCornerRadius" tools:ignore="NewApi">?attr/sucFooterBarButtonCornerRadius</item>
@@ -65,7 +65,7 @@
         <item name="android:theme">@style/SucPartnerCustomizationButton.Secondary</item>
 
         <!-- Values used in styles -->
-        <item name="android:fontFamily">?attr/sucFooterBarButtonFontFamily</item>
+        <item name="android:fontFamily" tools:targetApi="jelly_bean">?attr/sucFooterBarButtonFontFamily</item>
         <item name="android:minWidth">0dp</item>
         <item name="android:paddingLeft">?attr/sucFooterButtonPaddingStart</item>
         <item name="android:paddingStart" tools:ignore="NewApi">?attr/sucFooterButtonPaddingStart</item>
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index 442e86c..c9a1966 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -456,6 +456,10 @@
   CONFIG_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED(
       PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, ResourceType.BOOL),
 
+  // Waiting for the animation finished before process to the next page/action.
+  CONFIG_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED, ResourceType.BOOL),
+
   // The margin top of progress bar.
   CONFIG_PROGRESS_BAR_MARGIN_TOP(
       PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, ResourceType.DIMENSION),
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
index 3525fa1..aca9a07 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -35,6 +35,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig.ResourceType;
+import com.google.android.setupcompat.util.BuildCompatUtils;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumMap;
@@ -60,15 +61,23 @@
       "isExtendedPartnerConfigEnabled";
 
   @VisibleForTesting
+  public static final String IS_MATERIAL_YOU_STYLE_ENABLED_METHOD = "IsMaterialYouStyleEnabled";
+
+  @VisibleForTesting
   public static final String IS_DYNAMIC_COLOR_ENABLED_METHOD = "isDynamicColorEnabled";
 
   @VisibleForTesting
   public static final String IS_NEUTRAL_BUTTON_STYLE_ENABLED_METHOD = "isNeutralButtonStyleEnabled";
 
+  @VisibleForTesting public static final String SUW_PACKAGE_NAME = "com.google.android.setupwizard";
+  @VisibleForTesting public static final String MATERIAL_YOU_RESOURCE_SUFFIX = "_material_you";
+
   @VisibleForTesting static Bundle suwDayNightEnabledBundle = null;
 
   @VisibleForTesting public static Bundle applyExtendedPartnerConfigBundle = null;
 
+  @VisibleForTesting public static Bundle applyMaterialYouConfigBundle = null;
+
   @VisibleForTesting public static Bundle applyDynamicColorBundle = null;
 
   @VisibleForTesting public static Bundle applyNeutralButtonStyleBundle = null;
@@ -84,7 +93,8 @@
 
   private static int savedConfigUiMode;
 
-  private static int savedOrientation = Configuration.ORIENTATION_PORTRAIT;
+  @VisibleForTesting
+  public static int savedOrientation = Configuration.ORIENTATION_PORTRAIT;
 
   /**
    * When testing related to fake PartnerConfigHelper instance, should sync the following saved
@@ -538,9 +548,10 @@
     if (fallbackBundle != null) {
       resourceEntryBundle.putBundle(KEY_FALLBACK_CONFIG, fallbackBundle.getBundle(resourceName));
     }
-
-    return adjustResourceEntryDayNightMode(
-        context, ResourceEntry.fromBundle(context, resourceEntryBundle));
+    ResourceEntry adjustResourceEntry =
+        adjustResourceEntryDefaultValue(
+            context, ResourceEntry.fromBundle(context, resourceEntryBundle));
+    return adjustResourceEntryDayNightMode(context, adjustResourceEntry);
   }
 
   /**
@@ -565,11 +576,50 @@
     return resourceEntry;
   }
 
+  // Check the MNStyle flag and replace the inputResourceEntry.resourceName &
+  // inputResourceEntry.resourceId after T, that means if using Gliv4 before S, will always use
+  // glifv3 resources.
+  ResourceEntry adjustResourceEntryDefaultValue(Context context, ResourceEntry inputResourceEntry) {
+    if (BuildCompatUtils.isAtLeastT() && shouldApplyMaterialYouStyle(context)) {
+      // If not overlay resource
+      try {
+        if (SUW_PACKAGE_NAME.equals(inputResourceEntry.getPackageName())) {
+          String resourceTypeName =
+              inputResourceEntry
+                  .getResources()
+                  .getResourceTypeName(inputResourceEntry.getResourceId());
+          // try to update resourceName & resourceId
+          String materialYouResourceName =
+              inputResourceEntry.getResourceName().concat(MATERIAL_YOU_RESOURCE_SUFFIX);
+          int materialYouResourceId =
+              inputResourceEntry
+                  .getResources()
+                  .getIdentifier(
+                      materialYouResourceName,
+                      resourceTypeName,
+                      inputResourceEntry.getPackageName());
+          if (materialYouResourceId != 0) {
+            Log.i(TAG, "use material you resource:" + materialYouResourceName);
+            return new ResourceEntry(
+                inputResourceEntry.getPackageName(),
+                materialYouResourceName,
+                materialYouResourceId,
+                inputResourceEntry.getResources());
+          }
+        }
+      } catch (NotFoundException ex) {
+        // fall through
+      }
+    }
+    return inputResourceEntry;
+  }
+
   @VisibleForTesting
   public static synchronized void resetInstance() {
     instance = null;
     suwDayNightEnabledBundle = null;
     applyExtendedPartnerConfigBundle = null;
+    applyMaterialYouConfigBundle = null;
     applyDynamicColorBundle = null;
     applyNeutralButtonStyleBundle = null;
   }
@@ -628,6 +678,39 @@
             IS_EXTENDED_PARTNER_CONFIG_ENABLED_METHOD, false));
   }
 
+  /**
+   * Returns true if the SetupWizard is flow enabled "Material You(Glifv4)" style, or the result of
+   * shouldApplyExtendedPartnerConfig() in SDK S as fallback.
+   */
+  public static boolean shouldApplyMaterialYouStyle(@NonNull Context context) {
+    if (applyMaterialYouConfigBundle == null || applyMaterialYouConfigBundle.isEmpty()) {
+      try {
+        applyMaterialYouConfigBundle =
+            context
+                .getContentResolver()
+                .call(
+                    getContentUri(),
+                    IS_MATERIAL_YOU_STYLE_ENABLED_METHOD,
+                    /* arg= */ null,
+                    /* extras= */ null);
+        // The suw version did not support the flag yet, fallback to
+        // shouldApplyExtendedPartnerConfig() for SDK S.
+        if (applyMaterialYouConfigBundle != null
+            && applyMaterialYouConfigBundle.isEmpty()
+            && !BuildCompatUtils.isAtLeastT()) {
+          return shouldApplyExtendedPartnerConfig(context);
+        }
+      } catch (IllegalArgumentException | SecurityException exception) {
+        Log.w(TAG, "SetupWizard Material You configs supporting status unknown; return as false.");
+        applyMaterialYouConfigBundle = null;
+        return false;
+      }
+    }
+
+    return (applyMaterialYouConfigBundle != null
+        && applyMaterialYouConfigBundle.getBoolean(IS_MATERIAL_YOU_STYLE_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) {
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index cbf72f5..9554ff3 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -138,6 +138,7 @@
   PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM,
   PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT,
   PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED,
   PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP,
   PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM,
 })
@@ -403,10 +404,10 @@
   // 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.
+  // The intrinsic width of the card view for foldable/tablet.
   String KEY_CARD_VIEW_INTRINSIC_WIDTH = "setup_design_card_view_intrinsic_width";
 
-  // The intrinsic height of the card view for foldabe/tablet.
+  // The intrinsic height of the card view for foldable/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.
@@ -520,6 +521,10 @@
   String KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED =
       "loading_layout_full_screen_illustration_enabled";
 
+  // Waiting the animation finished before process to the next page/action.
+  String KEY_LOADING_LAYOUT_WAIT_FOR_ANIMATION_FINISHED =
+      "loading_layout_wait_for_animation_finished";
+
   // A margin top of the content frame of progress bar.
   String KEY_PROGRESS_BAR_MARGIN_TOP = "setup_design_progress_bar_margin_top";
 
diff --git a/setup_extension/info_modules.proto b/setup_extension/info_modules.proto
new file mode 100644
index 0000000..5027c4c
--- /dev/null
+++ b/setup_extension/info_modules.proto
@@ -0,0 +1,96 @@
+// Copyright (C) 2022 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.
+//
+// Author: yuchaoyu@google.com (Yu Chao)
+
+syntax = "proto2";
+
+package portal_tips_proto;
+
+option java_package = "com.google.android.setupcompat";
+option java_outer_classname = "InfoModules";
+
+// A list of informational modules to be displayed under the deferred setup
+// items.
+// Next index: 5
+message InfoModuleList {
+  // Contains the title of the entire info module section. This is displayed
+  // before the subtitle and the list of info modules.
+  optional string section_title = 1;
+
+  // Contains the subtitle of the entire info module section. This is displayed
+  // before the list of info modules.
+  optional string section_subtitle = 2;
+
+  // A module represents a single informational item to be displayed in the list
+  // under the deferred setup page.
+  //
+  // Each module contains a unique id, three sections of text labels (title,
+  // description, footer), the Intent URI to launch when being clicked, the raw
+  // icon, and a boolean to determine whether or not the icon should be colored.
+  // Next index: 8
+  message InfoModule {
+
+    // The id to uniquely identify an InfoModule.
+    optional string id = 1;
+
+    // Contains the title of the module. This is displayed as the first section
+    // of the text content.
+    optional string title = 2;
+
+    // Contains the main paragraphs of the module. This is displayed as the
+    // second section of the text content.
+    optional string description = 3;
+
+    // Contains the footer text of the module. This is displayed as the third
+    // and final section of the text content.
+    optional string footer = 4;
+
+    // Contains the Intent string to be launched when the module is clicked.
+    //
+    // This string could be generated from an `Intent` object through the
+    // `toUri()` method.
+    optional string intent_uri = 5;
+
+    // Contains the icon of the module in raw bytes of bitmap format.
+    optional bytes raw_icon = 6;
+
+    // Determines whether the icon should be colored according to the theme.
+    // Generally speaking, this should be set to true for monoline icons (such
+    // as Material Icons) because their colors change according to the theme.
+    // For icons which their colors should be preserved (such as package icons),
+    // this should be set to false.
+    optional bool should_apply_theme_color_on_icon = 7;
+  }
+
+  // Contains a list of informational modules to be displayed.
+  repeated InfoModule info_module_list = 3;
+
+  // A trailing link is a button item appearing below the info module list.
+  // Next index: 3
+  message TrailingLink {
+    // Contains the text that should be displayed on the link button.
+    optional string text = 1;
+
+    // Contains the Intent string to be launched when the link button is
+    // clicked.
+    //
+    // This string could be generated from an `Intent` object through the
+    // `toUri()` method.
+    optional string intent_uri = 2;
+  }
+
+  // Contains a trailing link to be displayed below the info modules.
+  optional TrailingLink trailing_link = 4;
+}