Import updated Android SetupCompat Library 404131397 am: 9eb31bddd3 am: dc56375967

Original change: https://googleplex-android-review.googlesource.com/c/platform/external/setupcompat/+/16084494

Change-Id: I4c386b9636a4bf701379c2441a80178b0f67160a
diff --git a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
index fad5cbf..8e23c1a 100644
--- a/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
+++ b/main/java/com/google/android/setupcompat/internal/FooterButtonPartnerConfig.java
@@ -24,6 +24,7 @@
   private final PartnerConfig buttonBackgroundConfig;
   private final PartnerConfig buttonDisableAlphaConfig;
   private final PartnerConfig buttonDisableBackgroundConfig;
+  private final PartnerConfig buttonDisableTextColorConfig;
   private final PartnerConfig buttonIconConfig;
   private final PartnerConfig buttonTextColorConfig;
   private final PartnerConfig buttonMarginStartConfig;
@@ -40,6 +41,7 @@
       PartnerConfig buttonBackgroundConfig,
       PartnerConfig buttonDisableAlphaConfig,
       PartnerConfig buttonDisableBackgroundConfig,
+      PartnerConfig buttonDisableTextColorConfig,
       PartnerConfig buttonIconConfig,
       PartnerConfig buttonTextColorConfig,
       PartnerConfig buttonMarginStartConfig,
@@ -60,6 +62,7 @@
     this.buttonBackgroundConfig = buttonBackgroundConfig;
     this.buttonDisableAlphaConfig = buttonDisableAlphaConfig;
     this.buttonDisableBackgroundConfig = buttonDisableBackgroundConfig;
+    this.buttonDisableTextColorConfig = buttonDisableTextColorConfig;
     this.buttonRadiusConfig = buttonRadiusConfig;
     this.buttonIconConfig = buttonIconConfig;
     this.buttonRippleColorAlphaConfig = buttonRippleColorAlphaConfig;
@@ -81,6 +84,10 @@
     return buttonDisableBackgroundConfig;
   }
 
+  public PartnerConfig getButtonDisableTextColorConfig() {
+    return buttonDisableTextColorConfig;
+  }
+
   public PartnerConfig getButtonIconConfig() {
     return buttonIconConfig;
   }
@@ -123,6 +130,7 @@
     private PartnerConfig buttonBackgroundConfig = null;
     private PartnerConfig buttonDisableAlphaConfig = null;
     private PartnerConfig buttonDisableBackgroundConfig = null;
+    private PartnerConfig buttonDisableTextColorConfig = null;
     private PartnerConfig buttonIconConfig = null;
     private PartnerConfig buttonTextColorConfig = null;
     private PartnerConfig buttonMarginStartConfig = null;
@@ -157,6 +165,11 @@
       return this;
     }
 
+    public Builder setButtonDisableTextColorConfig(PartnerConfig buttonDisableTextColorConfig) {
+      this.buttonDisableTextColorConfig = buttonDisableTextColorConfig;
+      return this;
+    }
+
     public Builder setButtonIconConfig(PartnerConfig buttonIconConfig) {
       this.buttonIconConfig = buttonIconConfig;
       return this;
@@ -213,6 +226,7 @@
           buttonBackgroundConfig,
           buttonDisableAlphaConfig,
           buttonDisableBackgroundConfig,
+          buttonDisableTextColorConfig,
           buttonIconConfig,
           buttonTextColorConfig,
           buttonMarginStartConfig,
diff --git a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
index 52e3dc9..33747d4 100644
--- a/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
+++ b/main/java/com/google/android/setupcompat/template/FooterBarMixin.java
@@ -21,7 +21,6 @@
 import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -79,8 +78,6 @@
   private FooterButton secondaryButton;
   @IdRes private int primaryButtonId;
   @IdRes private int secondaryButtonId;
-  ColorStateList primaryDefaultTextColor = null;
-  ColorStateList secondaryDefaultTextColor = null;
   @VisibleForTesting public FooterButtonPartnerConfig primaryButtonPartnerConfigForTesting;
   @VisibleForTesting public FooterButtonPartnerConfig secondaryButtonPartnerConfigForTesting;
 
@@ -109,11 +106,15 @@
           if (button != null) {
             button.setEnabled(enabled);
             if (applyPartnerResources && !applyDynamicColor) {
-              updateButtonTextColorWithEnabledState(
+
+              updateButtonTextColorWithStates(
                   button,
                   (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
                       ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR
-                      : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR);
+                      : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
+                  (id == primaryButtonId || isSecondaryButtonInPrimaryStyle)
+                      ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR
+                      : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR);
             }
           }
         }
@@ -221,6 +222,8 @@
       metrics.logSecondaryButtonInitialStateVisibility(
           /* isVisible= */ true, /* isUsingXml= */ true);
     }
+
+    FooterButtonStyleUtils.clearSavedDefaultTextColor();
   }
 
   private boolean isFooterButtonAlignedEnd() {
@@ -390,6 +393,8 @@
             .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
             .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
             .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+            .setButtonDisableTextColorConfig(
+                PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR)
             .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType()))
             .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
             .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
@@ -404,11 +409,9 @@
     FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
     // update information for primary button. Need to update as long as the button inflated.
     primaryButtonId = button.getId();
-    primaryDefaultTextColor = button.getTextColors();
     button.setPrimaryButtonStyle(/* isPrimaryButtonStyle= */ true);
     primaryButton = footerButton;
     primaryButtonPartnerConfigForTesting = footerButtonPartnerConfig;
-
     onFooterButtonInflated(button, footerBarPrimaryBackgroundColor);
     onFooterButtonApplyPartnerResource(button, footerButtonPartnerConfig);
 
@@ -463,6 +466,10 @@
                     : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
             .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
             .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+            .setButtonDisableTextColorConfig(
+                usePrimaryStyle
+                    ? PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR
+                    : PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR)
             .setButtonIconConfig(getDrawablePartnerConfig(footerButton.getButtonType()))
             .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
             .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
@@ -480,7 +487,6 @@
     FooterActionButton button = inflateButton(footerButton, footerButtonPartnerConfig);
     // update information for secondary button. Need to update as long as the button inflated.
     secondaryButtonId = button.getId();
-    secondaryDefaultTextColor = button.getTextColors();
     button.setPrimaryButtonStyle(usePrimaryStyle);
     secondaryButton = footerButton;
     secondaryButtonPartnerConfigForTesting = footerButtonPartnerConfig;
@@ -723,22 +729,23 @@
         footerButtonPartnerConfig);
     if (!applyDynamicColor) {
       // adjust text color based on enabled state
-      updateButtonTextColorWithEnabledState(
-          button, footerButtonPartnerConfig.getButtonTextColorConfig());
+      updateButtonTextColorWithStates(
+          button,
+          footerButtonPartnerConfig.getButtonTextColorConfig(),
+          footerButtonPartnerConfig.getButtonDisableTextColorConfig());
     }
   }
 
-  private void updateButtonTextColorWithEnabledState(
-      Button button, PartnerConfig buttonTextColorConfig) {
+  private void updateButtonTextColorWithStates(
+      Button button,
+      PartnerConfig buttonTextColorConfig,
+      PartnerConfig buttonTextDisabledColorConfig) {
     if (button.isEnabled()) {
       FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
           context, button, buttonTextColorConfig);
     } else {
-      FooterButtonStyleUtils.updateButtonTextDisableColor(
-          button,
-          /* is Primary= */ (primaryButtonId == button.getId() || isSecondaryButtonInPrimaryStyle)
-              ? primaryDefaultTextColor
-              : secondaryDefaultTextColor);
+      FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig(
+          context, button, buttonTextDisabledColorConfig);
     }
   }
 
diff --git a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
index cc445a6..093de3e 100644
--- a/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
+++ b/main/java/com/google/android/setupcompat/template/FooterButtonStyleUtils.java
@@ -41,11 +41,14 @@
 import com.google.android.setupcompat.internal.Preconditions;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+import java.util.HashMap;
 
 /** Utils for updating the button style. */
 public class FooterButtonStyleUtils {
   private static final float DEFAULT_DISABLED_ALPHA = 0.26f;
 
+  private static final HashMap<Integer, ColorStateList> defaultTextColor = new HashMap<>();
+
   /** Apply the partner primary button style to given {@code button}. */
   public static void applyPrimaryButtonPartnerResource(
       Context context, Button button, boolean applyDynamicColor) {
@@ -56,6 +59,8 @@
             .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR)
             .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
             .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+            .setButtonDisableTextColorConfig(
+                PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR)
             .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
             .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
             .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_PRIMARY_BUTTON_TEXT_COLOR)
@@ -91,6 +96,8 @@
             .setButtonBackgroundConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_BG_COLOR)
             .setButtonDisableAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_ALPHA)
             .setButtonDisableBackgroundConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR)
+            .setButtonDisableTextColorConfig(
+                PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR)
             .setButtonRadiusConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RADIUS)
             .setButtonRippleColorAlphaConfig(PartnerConfig.CONFIG_FOOTER_BUTTON_RIPPLE_COLOR_ALPHA)
             .setTextColorConfig(PartnerConfig.CONFIG_FOOTER_SECONDARY_BUTTON_TEXT_COLOR)
@@ -115,6 +122,9 @@
       boolean isButtonIconAtEnd,
       FooterButtonPartnerConfig footerButtonPartnerConfig) {
 
+    // Save defualt text color for the partner config disable button text color not available.
+    saveButtonDefaultTextColor(button);
+
     // If dynamic color enabled, these colors won't be overrode by partner config.
     // Instead, these colors align with the current theme colors.
     if (!applyDynamicColor) {
@@ -122,6 +132,9 @@
       if (button.isEnabled()) {
         FooterButtonStyleUtils.updateButtonTextEnabledColorWithPartnerConfig(
             context, button, footerButtonPartnerConfig.getButtonTextColorConfig());
+      } else {
+        FooterButtonStyleUtils.updateButtonTextDisabledColorWithPartnerConfig(
+            context, button, footerButtonPartnerConfig.getButtonDisableTextColorConfig());
       }
       FooterButtonStyleUtils.updateButtonBackgroundWithPartnerConfig(
           context,
@@ -166,10 +179,24 @@
     }
   }
 
-  static void updateButtonTextDisableColor(Button button, ColorStateList disabledTextColor) {
-    // TODO : add disable footer button text color partner config
+  static void updateButtonTextDisabledColorWithPartnerConfig(
+      Context context, Button button, PartnerConfig buttonDisableTextColorConfig) {
+    if (PartnerConfigHelper.get(context).isPartnerConfigAvailable(buttonDisableTextColorConfig)) {
+      @ColorInt
+      int color = PartnerConfigHelper.get(context).getColor(context, buttonDisableTextColorConfig);
+      updateButtonTextDisabledColor(button, color);
+    } else {
+      updateButtonTextDisableDefaultColor(button, getButtonDefaultTextCorlor(button));
+    }
+  }
 
-    // disable state will use the default disable state color
+  static void updateButtonTextDisabledColor(Button button, @ColorInt int textColor) {
+    if (textColor != Color.TRANSPARENT) {
+      button.setTextColor(ColorStateList.valueOf(textColor));
+    }
+  }
+
+  static void updateButtonTextDisableDefaultColor(Button button, ColorStateList disabledTextColor) {
     button.setTextColor(disabledTextColor);
   }
 
@@ -385,6 +412,21 @@
     button.getBackground().mutate().setColorFilter(color, Mode.SRC_ATOP);
   }
 
+  private static void saveButtonDefaultTextColor(Button button) {
+    defaultTextColor.put(button.getId(), button.getTextColors());
+  }
+
+  private static ColorStateList getButtonDefaultTextCorlor(Button button) {
+    if (!defaultTextColor.containsKey(button.getId())) {
+      throw new IllegalStateException("There is no saved default color for button");
+    }
+    return defaultTextColor.get(button.getId());
+  }
+
+  static void clearSavedDefaultTextColor() {
+    defaultTextColor.clear();
+  }
+
   @VisibleForTesting
   public static GradientDrawable getGradientDrawable(Button button) {
     // RippleDrawable is available after sdk 21, InsetDrawable#getDrawable is available after
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
index 54a2ef8..a9cd4a0 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfig.java
@@ -128,6 +128,14 @@
   CONFIG_FOOTER_BUTTON_DISABLED_BG_COLOR(
       PartnerConfigKey.KEY_FOOTER_BUTTON_DISABLED_BG_COLOR, ResourceType.COLOR),
 
+  // Disabled text color of the primary footer button
+  CONFIG_FOOTER_PRIMARY_BUTTON_DISABLED_TEXT_COLOR(
+      PartnerConfigKey.KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR, ResourceType.COLOR),
+
+  // Disabled text color of the secondary footer button
+  CONFIG_FOOTER_SECONDARY_BUTTON_DISABLED_TEXT_COLOR(
+      PartnerConfigKey.KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR, ResourceType.COLOR),
+
   // Background color of the primary footer button
   CONFIG_FOOTER_PRIMARY_BUTTON_BG_COLOR(
       PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR, ResourceType.COLOR),
@@ -315,11 +323,11 @@
   // The divider of list items are showing on the pages.
   CONFIG_ITEMS_DIVIDER_SHOWN(PartnerConfigKey.KEY_ITEMS_DIVIDER_SHOWN, ResourceType.BOOL),
 
-  // The intrinsic width of the card view for foldabe/tablet.
+  // The intrinsic width of the card view for foldable/tablet.
   CONFIG_CARD_VIEW_INTRINSIC_WIDTH(
       PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_WIDTH, ResourceType.DIMENSION),
 
-  // The intrinsic height of the card view for foldabe/tablet.
+  // The intrinsic height of the card view for foldable/tablet.
   CONFIG_CARD_VIEW_INTRINSIC_HEIGHT(
       PartnerConfigKey.KEY_CARD_VIEW_INTRINSIC_HEIGHT, ResourceType.DIMENSION),
 
@@ -440,6 +448,10 @@
   CONFIG_LOADING_LAYOUT_HEADER_HEIGHT(
       PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT, ResourceType.DIMENSION),
 
+  // Use the fullscreen style lottie animation.
+  CONFIG_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED(
+      PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED, ResourceType.BOOL),
+
   // The margin top of progress bar.
   CONFIG_PROGRESS_BAR_MARGIN_TOP(
       PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP, ResourceType.DIMENSION),
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
index c0be011..ef39f9f 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigHelper.java
@@ -517,6 +517,8 @@
                     /* arg= */ null,
                     /* extras= */ null);
         partnerResourceCache.clear();
+        Log.i(
+            TAG, "PartnerConfigsBundle=" + (resultBundle != null ? resultBundle.size() : "(null)"));
       } catch (IllegalArgumentException | SecurityException exception) {
         Log.w(TAG, "Fail to get config from suw provider");
       }
diff --git a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
index bbededa..b6a3302 100644
--- a/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
+++ b/partnerconfig/java/com/google/android/setupcompat/partnerconfig/PartnerConfigKey.java
@@ -55,9 +55,11 @@
   PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_BG_COLOR,
   PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_TEXT_COLOR,
   PartnerConfigKey.KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START,
+  PartnerConfigKey.KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR,
   PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR,
   PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_TEXT_COLOR,
   PartnerConfigKey.KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START,
+  PartnerConfigKey.KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR,
   PartnerConfigKey.KEY_LAYOUT_BACKGROUND_COLOR,
   PartnerConfigKey.KEY_LAYOUT_MARGIN_START,
   PartnerConfigKey.KEY_LAYOUT_MARGIN_END,
@@ -134,6 +136,7 @@
   PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_END,
   PartnerConfigKey.KEY_LOADING_LAYOUT_CONTENT_PADDING_BOTTOM,
   PartnerConfigKey.KEY_LOADING_LAYOUT_HEADER_HEIGHT,
+  PartnerConfigKey.KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED,
   PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_TOP,
   PartnerConfigKey.KEY_PROGRESS_BAR_MARGIN_BOTTOM,
 })
@@ -236,6 +239,9 @@
   // Margin start of the primary footer button
   String KEY_FOOTER_PRIMARY_BUTTON_MARGIN_START = "setup_compat_footer_primary_button_margin_start";
 
+  // Disabled text color of the primary footer button
+  String KEY_PRIMARY_BUTTON_DISABLED_TEXT_COLOR = "setup_compat_primary_button_disabled_text_color";
+
   // Background color of the secondary footer button
   String KEY_FOOTER_SECONDARY_BUTTON_BG_COLOR = "setup_compat_footer_secondary_button_bg_color";
 
@@ -246,6 +252,10 @@
   String KEY_FOOTER_SECONDARY_BUTTON_MARGIN_START =
       "setup_compat_footer_secondary_button_margin_start";
 
+  // Disabled text color of the secondary footer button
+  String KEY_SECONDARY_BUTTON_DISABLED_TEXT_COLOR =
+      "setup_compat_secondary_button_disabled_text_color";
+
   // Background color of layout
   String KEY_LAYOUT_BACKGROUND_COLOR = "setup_design_layout_bg_color";
 
@@ -502,6 +512,10 @@
   // A height of the header of loading layout.
   String KEY_LOADING_LAYOUT_HEADER_HEIGHT = "loading_layout_header_height";
 
+  // Use the fullscreen style lottie animation.
+  String KEY_LOADING_LAYOUT_FULL_SCREEN_ILLUSTRATION_ENABLED =
+      "loading_layout_full_screen_illustration_enabled";
+
   // A margin top of the content frame of progress bar.
   String KEY_PROGRESS_BAR_MARGIN_TOP = "setup_design_progress_bar_margin_top";
 
diff --git a/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl b/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl
new file mode 100644
index 0000000..2b83576
--- /dev/null
+++ b/portal_extension/aidl/com/google/android/setupcompat/portal/ISetupNotificationServicePortalExtension.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import com.google.android.setupcompat.portal.IPortalProgressCallback;
+import com.google.android.setupcompat.portal.TaskComponent;
+
+/**
+ * Declares the interface for portal used by GmsCore.
+ */
+interface ISetupNotificationServicePortalExtension {
+  IPortalProgressCallback registerTask(in TaskComponent component) = 1;
+
+  boolean removeTask(String packageName, String taskName) = 2;
+}
\ No newline at end of file
diff --git a/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl b/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl
new file mode 100644
index 0000000..8a4dfb3
--- /dev/null
+++ b/portal_extension/aidl/com/google/android/setupcompat/portal/TaskComponent.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+/**
+ * A class that represents how a persistent notification is to be presented to the user using the
+ * {@link com.google.android.setupcompat.portal.ISetupNotificationServicePortalExtension }.
+ */
+parcelable TaskComponent;
\ No newline at end of file
diff --git a/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java b/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java
new file mode 100644
index 0000000..4f1b884
--- /dev/null
+++ b/portal_extension/java/com/google/android/setupcompat/portal/PortalExtensionConstants.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+/** Constant values used for PortalExtension */
+public class PortalExtensionConstants {
+  public static final String BIND_SERVICE_INTENT_ACTION =
+      "com.google.android.setupcompat.portal.SetupNotificationService.BIND_EXTENSION";
+
+  private PortalExtensionConstants() {}
+  ;
+}
diff --git a/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java b/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java
new file mode 100644
index 0000000..16d35a3
--- /dev/null
+++ b/portal_extension/java/com/google/android/setupcompat/portal/TaskComponent.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.setupcompat.portal;
+
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+import com.google.android.setupcompat.internal.Preconditions;
+
+/**
+ * A class that represents how a persistent notification is to be presented to the user using the
+ * {@link com.google.android.setupcompat.portal.ISetupNotificationServicePortalExtension }.
+ */
+public class TaskComponent implements Parcelable {
+  private final String packageName;
+  private final String taskName;
+  @StringRes private final int displayNameResId;
+  @DrawableRes private final int displayIconResId;
+  private final Intent itemClickIntent;
+
+  private TaskComponent(
+      String packageName,
+      String taskName,
+      @StringRes int displayNameResId,
+      @DrawableRes int displayIconResId,
+      Intent itemClickIntent) {
+    this.packageName = packageName;
+    this.taskName = taskName;
+    this.displayNameResId = displayNameResId;
+    this.displayIconResId = displayIconResId;
+    this.itemClickIntent = itemClickIntent;
+  }
+
+  /** Returns a new instance of {@link Builder}. */
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  /** Returns the package name where the service exist. */
+  @NonNull
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /** Returns the service class name */
+  @NonNull
+  public String getTaskName() {
+    return taskName;
+  }
+
+  /** Returns the string resource id of display name. */
+  @StringRes
+  public int getDisplayName() {
+    return displayNameResId;
+  }
+
+  /** Returns the drawable resource id of display icon. */
+  @DrawableRes
+  public int getDisplayIcon() {
+    return displayIconResId;
+  }
+
+  /** Returns the Intent to start the user interface while progress item click. */
+  public Intent getItemClickIntent() {
+    return itemClickIntent;
+  }
+
+  @Override
+  public void writeToParcel(Parcel dest, int flags) {
+    dest.writeString(getPackageName());
+    dest.writeString(getTaskName());
+    dest.writeInt(getDisplayName());
+    dest.writeInt(getDisplayIcon());
+    dest.writeParcelable(getItemClickIntent(), 0);
+  }
+
+  @Override
+  public int describeContents() {
+    return 0;
+  }
+
+  public static final Creator<TaskComponent> CREATOR =
+      new Creator<TaskComponent>() {
+        @Override
+        public TaskComponent createFromParcel(Parcel in) {
+          return TaskComponent.newBuilder()
+              .setPackageName(in.readString())
+              .setTaskName(in.readString())
+              .setDisplayName(in.readInt())
+              .setDisplayIcon(in.readInt())
+              .setItemClickIntent(in.readParcelable(Intent.class.getClassLoader()))
+              .build();
+        }
+
+        @Override
+        public TaskComponent[] newArray(int size) {
+          return new TaskComponent[size];
+        }
+      };
+
+  /** Builder class for {@link com.google.android.setupcompat.portal.TaskComponent} objects. */
+  public static class Builder {
+    private String packageName;
+    private String taskName;
+    @StringRes private int displayNameResId;
+    @DrawableRes private int displayIconResId;
+    private Intent itemClickIntent;
+
+    /** Sets the packages name which is the service exists */
+    public Builder setPackageName(@NonNull String packageName) {
+      this.packageName = packageName;
+      return this;
+    }
+
+    /** Sets a name to identify what task this progress is. */
+    public Builder setTaskName(@NonNull String taskName) {
+      this.taskName = taskName;
+      return this;
+    }
+
+    /** Sets the name which is displayed on PortalActivity */
+    public Builder setDisplayName(@StringRes int displayNameResId) {
+      this.displayNameResId = displayNameResId;
+      return this;
+    }
+
+    /** Sets the icon which is display on PortalActivity */
+    public Builder setDisplayIcon(@DrawableRes int displayIconResId) {
+      this.displayIconResId = displayIconResId;
+      return this;
+    }
+
+    public Builder setItemClickIntent(Intent itemClickIntent) {
+      this.itemClickIntent = itemClickIntent;
+      return this;
+    }
+
+    public TaskComponent build() {
+      Preconditions.checkNotNull(packageName, "packageName cannot be null.");
+      Preconditions.checkNotNull(taskName, "serviceClass cannot be null.");
+      Preconditions.checkNotNull(itemClickIntent, "Item click intent cannot be null");
+      Preconditions.checkArgument(displayNameResId != 0, "Invalidate resource id of display name");
+      Preconditions.checkArgument(displayIconResId != 0, "Invalidate resource id of display icon");
+
+      return new TaskComponent(
+          packageName, taskName, displayNameResId, displayIconResId, itemClickIntent);
+    }
+  }
+}