Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)

Bug: 166295507
Merged-In: I27c2ede56c89e9145b6f1a5cdd349fe37d3a229b
Change-Id: Ia4f3ce3113c6199975c391de8fbcf828cca63f13
diff --git a/main/res/layout/sud_glif_header.xml b/main/res/layout/sud_glif_header.xml
index 478438c..f5a1113 100644
--- a/main/res/layout/sud_glif_header.xml
+++ b/main/res/layout/sud_glif_header.xml
@@ -16,6 +16,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/sud_layout_header"
     style="@style/SudGlifHeaderContainer"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
diff --git a/main/res/layout/sud_glif_illustration_loading_screen.xml b/main/res/layout/sud_glif_illustration_loading_screen.xml
new file mode 100644
index 0000000..581d5c6
--- /dev/null
+++ b/main/res/layout/sud_glif_illustration_loading_screen.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+-->
+
+<com.google.android.setupdesign.GlifLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/setup_wizard_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:ignore="UnusedResources">
+    <!-- Ignore UnusedResources: can be used by clients -->
+
+    <LinearLayout
+        android:id="@+id/default_content_container"
+        style="@style/SudContentFrame"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/sud_layout_description"
+            style="@style/SudDescription.Glif"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="invisible"/>
+
+        <com.google.android.setupdesign.view.FillContentLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+
+            <com.google.android.setupdesign.view.IllustrationVideoView
+                android:id="@+id/sud_illustration_video_view"
+                style="@style/SudContentIllustration"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+
+        </com.google.android.setupdesign.view.FillContentLayout>
+
+    </LinearLayout>
+
+</com.google.android.setupdesign.GlifLayout>
diff --git a/main/src/com/google/android/setupdesign/GlifLayout.java b/main/src/com/google/android/setupdesign/GlifLayout.java
index 67be5d5..de4f8c1 100644
--- a/main/src/com/google/android/setupdesign/GlifLayout.java
+++ b/main/src/com/google/android/setupdesign/GlifLayout.java
@@ -112,11 +112,8 @@
         a.getBoolean(R.styleable.SudGlifLayout_sudUsePartnerHeavyTheme, false);
     applyPartnerHeavyThemeResource = shouldApplyPartnerResource() && usePartnerHeavyTheme;
 
-    registerMixin(
-        HeaderMixin.class,
-        new HeaderMixin(this, attrs, defStyleAttr));
-    registerMixin(
-        IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
+    registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr));
+    registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
     registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
     final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
     registerMixin(RequireScrollMixin.class, requireScrollMixin);
@@ -154,15 +151,19 @@
   @Override
   protected void onFinishInflate() {
     super.onFinishInflate();
+    getMixin(IconMixin.class).tryApplyPartnerCustomizationStyle();
+    getMixin(HeaderMixin.class).tryApplyPartnerCustomizationStyle();
+    tryApplyPartnerCustomizationStyleToShortDescription();
+  }
 
-    TextView description = this.findManagedViewById(R.id.sud_layout_description);
-    if (description != null) {
-      if (applyPartnerHeavyThemeResource) {
+  private void tryApplyPartnerCustomizationStyleToShortDescription() {
+    if (applyPartnerHeavyThemeResource) {
+      TextView description =
+          this.findManagedViewById(com.google.android.setupdesign.R.id.sud_layout_description);
+      if (description != null) {
         DescriptionStyler.applyPartnerCustomizationStyle(description);
       }
     }
-    getMixin(IconMixin.class).applyPartnerCustomizationStyle();
-    getMixin(HeaderMixin.class).applyPartnerCustomizationStyle();
   }
 
   @Override
diff --git a/main/src/com/google/android/setupdesign/util/LinkAccessibilityHelper.java b/main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java
similarity index 99%
rename from main/src/com/google/android/setupdesign/util/LinkAccessibilityHelper.java
rename to main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java
index fd0d17f..ed9daf3 100644
--- a/main/src/com/google/android/setupdesign/util/LinkAccessibilityHelper.java
+++ b/main/src/com/google/android/setupdesign/accessibility/LinkAccessibilityHelper.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.google.android.setupdesign.util;
+package com.google.android.setupdesign.accessibility;
 
 import android.graphics.Rect;
 import android.os.Build;
diff --git a/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java b/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java
index 28b41c9..42438d6 100644
--- a/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java
+++ b/main/src/com/google/android/setupdesign/items/ExpandableSwitchItem.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
 import androidx.core.view.AccessibilityDelegateCompat;
 import androidx.core.view.ViewCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -64,6 +65,22 @@
                   ? AccessibilityActionCompat.ACTION_COLLAPSE
                   : AccessibilityActionCompat.ACTION_EXPAND);
         }
+
+        @Override
+        public boolean performAccessibilityAction(View view, int action, Bundle args) {
+          boolean result;
+          switch (action) {
+            case AccessibilityNodeInfoCompat.ACTION_COLLAPSE:
+            case AccessibilityNodeInfoCompat.ACTION_EXPAND:
+              setExpanded(!isExpanded());
+              result = true;
+              break;
+            default:
+              result = super.performAccessibilityAction(view, action, args);
+              break;
+          }
+          return result;
+        }
       };
 
   public ExpandableSwitchItem() {
diff --git a/main/src/com/google/android/setupdesign/items/Item.java b/main/src/com/google/android/setupdesign/items/Item.java
index 6f0cea3..e5d173f 100644
--- a/main/src/com/google/android/setupdesign/items/Item.java
+++ b/main/src/com/google/android/setupdesign/items/Item.java
@@ -21,6 +21,7 @@
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
@@ -37,10 +38,10 @@
 public class Item extends AbstractItem {
 
   private boolean enabled = true;
-  private Drawable icon;
+  @Nullable private Drawable icon;
   private int layoutRes;
-  private CharSequence summary;
-  private CharSequence title;
+  @Nullable private CharSequence summary;
+  @Nullable private CharSequence title;
   private boolean visible = true;
   @ColorInt private int iconTint = Color.TRANSPARENT;
   private int iconGravity = Gravity.CENTER_VERTICAL;
@@ -83,11 +84,12 @@
     return enabled;
   }
 
-  public void setIcon(Drawable icon) {
+  public void setIcon(@Nullable Drawable icon) {
     this.icon = icon;
     notifyItemChanged();
   }
 
+  @Nullable
   public Drawable getIcon() {
     return icon;
   }
@@ -119,20 +121,22 @@
     return layoutRes;
   }
 
-  public void setSummary(CharSequence summary) {
+  public void setSummary(@Nullable CharSequence summary) {
     this.summary = summary;
     notifyItemChanged();
   }
 
+  @Nullable
   public CharSequence getSummary() {
     return summary;
   }
 
-  public void setTitle(CharSequence title) {
+  public void setTitle(@Nullable CharSequence title) {
     this.title = title;
     notifyItemChanged();
   }
 
+  @Nullable
   public CharSequence getTitle() {
     return title;
   }
diff --git a/main/src/com/google/android/setupdesign/template/HeaderMixin.java b/main/src/com/google/android/setupdesign/template/HeaderMixin.java
index 51b726b..0a6eb9e 100644
--- a/main/src/com/google/android/setupdesign/template/HeaderMixin.java
+++ b/main/src/com/google/android/setupdesign/template/HeaderMixin.java
@@ -16,28 +16,20 @@
 
 package com.google.android.setupdesign.template;
 
-import static android.content.res.ColorStateList.valueOf;
-
-import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
-import android.graphics.Typeface;
 import androidx.annotation.AttrRes;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
 import android.view.ViewParent;
 import android.widget.LinearLayout;
 import android.widget.TextView;
-import com.google.android.setupdesign.R;
 import com.google.android.setupcompat.internal.TemplateLayout;
-import com.google.android.setupcompat.partnerconfig.PartnerConfig;
-import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
 import com.google.android.setupcompat.template.Mixin;
-import com.google.android.setupdesign.GlifLayout;
-import java.util.Locale;
+import com.google.android.setupdesign.R;
+import com.google.android.setupdesign.util.HeaderAreaStyler;
+import com.google.android.setupdesign.util.PartnerStyleHelper;
 
 /**
  * A {@link com.google.android.setupcompat.template.Mixin} for setting and getting the header text.
@@ -75,64 +67,22 @@
     a.recycle();
   }
 
-  /** See {@link #applyPartnerCustomizationStyle(Context, TextView)}. */
-  public void applyPartnerCustomizationStyle() {
-    final Context context = templateLayout.getContext();
-    TextView header = templateLayout.findManagedViewById(R.id.suc_layout_title);
-    applyPartnerCustomizationStyle(context, header);
-  }
-
   /**
-   * Use the given {@code header} to apply heavy theme. If {@link
-   * com.google.android.setupdesign.GlifLayout#shouldApplyPartnerHeavyThemeResource()} is true,
-   * {@code header} can be customized style from partner configuration.
-   *
-   * @param context The context of client activity.
-   * @param header The icon image to use for apply heavy theme.
+   * Tries to apply the partner customizations to the header text and background if the layout of
+   * this {@link HeaderMixin} is set to apply partner heavy theme resource.
    */
-  private void applyPartnerCustomizationStyle(Context context, @Nullable TextView header) {
-    if (header != null
-        && (templateLayout instanceof GlifLayout)
-        && ((GlifLayout) templateLayout).shouldApplyPartnerHeavyThemeResource()) {
-      int textColor =
-          PartnerConfigHelper.get(context)
-              .getColor(context, PartnerConfig.CONFIG_HEADER_TEXT_COLOR);
-      if (textColor != 0) {
-        setTextColor(valueOf(textColor));
-      }
+  public void tryApplyPartnerCustomizationStyle() {
+    if (!PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout)) {
+      return;
+    }
 
-      float textSize =
-          PartnerConfigHelper.get(context)
-              .getDimension(context, PartnerConfig.CONFIG_HEADER_TEXT_SIZE);
-      if (textSize != 0) {
-        setTextSize(textSize);
-      }
-
-      String fontFamily =
-          PartnerConfigHelper.get(context)
-              .getString(context, PartnerConfig.CONFIG_HEADER_FONT_FAMILY);
-      if (fontFamily != null) {
-        setFontFamily(Typeface.create(fontFamily, Typeface.NORMAL));
-      }
-
-      String gravity =
-          PartnerConfigHelper.get(context).getString(context, PartnerConfig.CONFIG_LAYOUT_GRAVITY);
-      if (gravity != null) {
-        switch (gravity.toLowerCase(Locale.ROOT)) {
-          case "center":
-            setGravity(header, Gravity.CENTER);
-            break;
-          case "start":
-            setGravity(header, Gravity.START);
-            break;
-          default: // fall out
-        }
-      }
-
-      int color =
-          PartnerConfigHelper.get(context)
-              .getColor(context, PartnerConfig.CONFIG_HEADER_AREA_BACKGROUND_COLOR);
-      setBackgroundColor(color);
+    TextView header = templateLayout.findManagedViewById(R.id.suc_layout_title);
+    if (header != null) {
+      HeaderAreaStyler.applyPartnerCustomizationHeaderStyle(header);
+    }
+    LinearLayout headerLayout = templateLayout.findManagedViewById(R.id.sud_layout_header);
+    if (headerLayout != null) {
+      HeaderAreaStyler.applyPartnerCustomizationHeaderAreaStyle(headerLayout);
     }
   }
 
@@ -171,10 +121,11 @@
     return titleView != null ? titleView.getText() : null;
   }
 
-  private void setTextSize(float sizePx) {
+  /** Sets the visibility of header text */
+  public void setVisibility(int visibility) {
     final TextView titleView = getTextView();
     if (titleView != null) {
-      titleView.setTextSize(TypedValue.COMPLEX_UNIT_PX, sizePx);
+      titleView.setVisibility(visibility);
     }
   }
 
@@ -202,20 +153,9 @@
     }
   }
 
-  private void setFontFamily(Typeface fontFamily) {
-    final TextView titleView = getTextView();
-    if (titleView != null) {
-      titleView.setTypeface(fontFamily);
-    }
-  }
-
   /** Returns the current text color of the header. */
   public ColorStateList getTextColor() {
     final TextView titleView = getTextView();
     return titleView != null ? titleView.getTextColors() : null;
   }
-
-  private void setGravity(TextView header, int gravity) {
-    header.setGravity(gravity);
-  }
 }
diff --git a/main/src/com/google/android/setupdesign/template/IconMixin.java b/main/src/com/google/android/setupdesign/template/IconMixin.java
index cfc498d..dca934f 100644
--- a/main/src/com/google/android/setupdesign/template/IconMixin.java
+++ b/main/src/com/google/android/setupdesign/template/IconMixin.java
@@ -24,16 +24,14 @@
 import android.os.Build.VERSION_CODES;
 import androidx.annotation.ColorInt;
 import androidx.annotation.DrawableRes;
-import androidx.annotation.Nullable;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.widget.ImageView;
-import android.widget.LinearLayout;
 import com.google.android.setupcompat.internal.TemplateLayout;
 import com.google.android.setupcompat.template.Mixin;
-import com.google.android.setupdesign.GlifLayout;
 import com.google.android.setupdesign.R;
+import com.google.android.setupdesign.util.HeaderAreaStyler;
 import com.google.android.setupdesign.util.PartnerStyleHelper;
 
 /**
@@ -86,29 +84,18 @@
     a.recycle();
   }
 
-  /** See {@link #applyPartnerCustomizationStyle(Context, ImageView)}. */
-  public void applyPartnerCustomizationStyle() {
-    final Context context = templateLayout.getContext();
-    final ImageView iconImage = templateLayout.findManagedViewById(R.id.sud_layout_icon);
-    applyPartnerCustomizationStyle(context, iconImage);
-  }
-
   /**
-   * Use the given {@code iconImage} to apply heavy theme. If {@link
-   * com.google.android.setupdesign.GlifLayout#shouldApplyPartnerHeavyThemeResource()} is true,
-   * {@code iconImage} can be customized style from partner configuration.
-   *
-   * @param context The context of client activity.
-   * @param iconImage The icon image to use for apply heavy theme.
+   * Tries to apply the partner customization to the header icon if the layout of this {@link
+   * IconMixin} is set to apply partner heavy theme resource.
    */
-  private void applyPartnerCustomizationStyle(Context context, @Nullable ImageView iconImage) {
-    if (iconImage != null
-        && (templateLayout instanceof GlifLayout)
-        && ((GlifLayout) templateLayout).shouldApplyPartnerHeavyThemeResource()) {
-      int gravity = PartnerStyleHelper.getLayoutGravity(context);
-      if (gravity != 0) {
-        setGravity(iconImage, gravity);
-      }
+  public void tryApplyPartnerCustomizationStyle() {
+    if (!PartnerStyleHelper.isPartnerHeavyThemeLayout(templateLayout)) {
+      return;
+    }
+
+    ImageView iconImage = templateLayout.findManagedViewById(R.id.sud_layout_icon);
+    if (iconImage != null) {
+      HeaderAreaStyler.applyPartnerCustomizationIconStyle(iconImage);
     }
   }
 
@@ -185,16 +172,16 @@
     return iconView != null ? iconView.getContentDescription() : null;
   }
 
+  /** Sets the visibiltiy of the icon view */
+  public void setVisibility(int visibility) {
+    final ImageView iconView = getView();
+    if (iconView != null) {
+      iconView.setVisibility(visibility);
+    }
+  }
+
   /** @return The ImageView responsible for displaying the icon. */
   protected ImageView getView() {
     return (ImageView) templateLayout.findManagedViewById(R.id.sud_layout_icon);
   }
-
-  private void setGravity(ImageView icon, int gravity) {
-    if (icon.getLayoutParams() instanceof LinearLayout.LayoutParams) {
-      LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) icon.getLayoutParams();
-      layoutParams.gravity = gravity;
-      icon.setLayoutParams(layoutParams);
-    }
-  }
 }
diff --git a/main/src/com/google/android/setupdesign/template/ListMixin.java b/main/src/com/google/android/setupdesign/template/ListMixin.java
index 6e2cb20..38928aa 100644
--- a/main/src/com/google/android/setupdesign/template/ListMixin.java
+++ b/main/src/com/google/android/setupdesign/template/ListMixin.java
@@ -187,15 +187,17 @@
       if (defaultDivider == null) {
         defaultDivider = listView.getDivider();
       }
-      divider =
-          DrawableLayoutDirectionHelper.createRelativeInsetDrawable(
-              defaultDivider,
-              dividerInsetStart /* start */,
-              0 /* top */,
-              dividerInsetEnd /* end */,
-              0 /* bottom */,
-              templateLayout);
-      listView.setDivider(divider);
+      if (defaultDivider != null) {
+        divider =
+            DrawableLayoutDirectionHelper.createRelativeInsetDrawable(
+                defaultDivider,
+                dividerInsetStart /* start */,
+                0 /* top */,
+                dividerInsetEnd /* end */,
+                0 /* bottom */,
+                templateLayout);
+        listView.setDivider(divider);
+      }
     }
   }
 
diff --git a/main/src/com/google/android/setupdesign/util/ContentStyler.java b/main/src/com/google/android/setupdesign/util/ContentStyler.java
new file mode 100644
index 0000000..0ab49b8
--- /dev/null
+++ b/main/src/com/google/android/setupdesign/util/ContentStyler.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 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.setupdesign.util;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.widget.TextView;
+import com.google.android.setupcompat.partnerconfig.PartnerConfig;
+import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs;
+import java.util.Locale;
+
+/**
+ * Applies the partner style of content to the given TextView {@code contentText}. The user needs to
+ * check if the {@code contentText} should apply partner heavy theme before calling this method.
+ */
+public final class ContentStyler {
+  public static void applyPartnerCustomizationStyle(TextView contentText) {
+
+    TextViewPartnerStyler.applyPartnerCustomizationStyle(
+        contentText,
+        new TextPartnerConfigs(
+            PartnerConfig.CONFIG_CONTENT_TEXT_COLOR,
+            PartnerConfig.CONFIG_CONTENT_LINK_TEXT_COLOR,
+            PartnerConfig.CONFIG_CONTENT_TEXT_SIZE,
+            PartnerConfig.CONFIG_CONTENT_FONT_FAMILY,
+            ContentStyler.getPartnerContentTextGravity(contentText.getContext())));
+  }
+
+  public static int getPartnerContentTextGravity(Context context) {
+    String gravity =
+        PartnerConfigHelper.get(context)
+            .getString(context, PartnerConfig.CONFIG_CONTENT_LAYOUT_GRAVITY);
+    if (gravity == null) {
+      return 0;
+    }
+    switch (gravity.toLowerCase(Locale.ROOT)) {
+      case "center":
+        return Gravity.CENTER;
+      case "start":
+        return Gravity.START;
+      case "end":
+        return Gravity.END;
+      default:
+        return 0;
+    }
+  }
+
+  private ContentStyler() {}
+}
diff --git a/main/src/com/google/android/setupdesign/util/DescriptionStyler.java b/main/src/com/google/android/setupdesign/util/DescriptionStyler.java
index d4e7ded..0a05f9d 100644
--- a/main/src/com/google/android/setupdesign/util/DescriptionStyler.java
+++ b/main/src/com/google/android/setupdesign/util/DescriptionStyler.java
@@ -1,84 +1,43 @@
+/*
+ * Copyright (C) 2019 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.setupdesign.util;
 
-import android.content.Context;
-import android.graphics.Typeface;
-import androidx.annotation.VisibleForTesting;
-import android.util.TypedValue;
 import android.widget.TextView;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
-import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs;
 
-/** Applies the given style properties for the style of the given type. */
-public class DescriptionStyler {
+/**
+ * Applies the partner style of description to the given TextView {@code description}. The user
+ * needs to check if the {@code description} should apply partner heavy theme before calling this
+ * method.
+ */
+public final class DescriptionStyler {
 
   public static void applyPartnerCustomizationStyle(TextView description) {
 
-    final Context context = description.getContext();
-
-    int descriptionTextColor =
-        PartnerConfigHelper.get(context)
-            .getColor(context, PartnerConfig.CONFIG_DESCRIPTION_TEXT_COLOR);
-    if (descriptionTextColor != 0) {
-      setTextColor(description, descriptionTextColor);
-    }
-
-    int descriptionLinkTextColor =
-        PartnerConfigHelper.get(context)
-            .getColor(context, PartnerConfig.CONFIG_DESCRIPTION_LINK_TEXT_COLOR);
-    if (descriptionLinkTextColor != 0) {
-      setLinkTextColor(description, descriptionLinkTextColor);
-    }
-
-    float descriptionTextSize =
-        PartnerConfigHelper.get(context)
-            .getDimension(context, PartnerConfig.CONFIG_DESCRIPTION_TEXT_SIZE, 0);
-    if (descriptionTextSize != 0) {
-      setTextSize(description, descriptionTextSize);
-    }
-
-    String fontFamilyName =
-        PartnerConfigHelper.get(context)
-            .getString(context, PartnerConfig.CONFIG_DESCRIPTION_FONT_FAMILY);
-    Typeface font = Typeface.create(fontFamilyName, Typeface.NORMAL);
-    if (font != null) {
-      setFontFamily(description, font);
-    }
-
-    setGravity(description, PartnerStyleHelper.getLayoutGravity(context));
+    TextViewPartnerStyler.applyPartnerCustomizationStyle(
+        description,
+        new TextPartnerConfigs(
+            PartnerConfig.CONFIG_DESCRIPTION_TEXT_COLOR,
+            PartnerConfig.CONFIG_DESCRIPTION_LINK_TEXT_COLOR,
+            PartnerConfig.CONFIG_DESCRIPTION_TEXT_SIZE,
+            PartnerConfig.CONFIG_DESCRIPTION_FONT_FAMILY,
+            PartnerStyleHelper.getLayoutGravity(description.getContext())));
   }
 
-  @VisibleForTesting
-  static void setTextSize(TextView description, float size) {
-    if (description != null) {
-      description.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
-    }
-  }
-
-  @VisibleForTesting
-  static void setFontFamily(TextView description, Typeface fontFamily) {
-    if (description != null) {
-      description.setTypeface(fontFamily);
-    }
-  }
-
-  @VisibleForTesting
-  static void setTextColor(TextView description, int color) {
-    if (description != null) {
-      description.setTextColor(color);
-    }
-  }
-
-  @VisibleForTesting
-  static void setLinkTextColor(TextView description, int color) {
-    if (description != null) {
-      description.setLinkTextColor(color);
-    }
-  }
-
-  @VisibleForTesting
-  static void setGravity(TextView description, int gravity) {
-    if (description != null) {
-      description.setGravity(gravity);
-    }
-  }
+  private DescriptionStyler() {}
 }
diff --git a/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java b/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java
new file mode 100644
index 0000000..cd5135d
--- /dev/null
+++ b/main/src/com/google/android/setupdesign/util/HeaderAreaStyler.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.setupdesign.util;
+
+import android.content.Context;
+import androidx.annotation.Nullable;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.google.android.setupcompat.partnerconfig.PartnerConfig;
+import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+import com.google.android.setupdesign.util.TextViewPartnerStyler.TextPartnerConfigs;
+
+/**
+ * Helper class to apply the partner customization for the header area widgets. The user needs to
+ * check if the header area widgets should apply partner heavy theme before calling these methods.
+ */
+public final class HeaderAreaStyler {
+
+  /** Applies the partner style of header text to the given textView {@code header}. */
+  public static void applyPartnerCustomizationHeaderStyle(@Nullable TextView header) {
+
+    if (header == null) {
+      return;
+    }
+    TextViewPartnerStyler.applyPartnerCustomizationStyle(
+        header,
+        new TextPartnerConfigs(
+            PartnerConfig.CONFIG_HEADER_TEXT_COLOR,
+            null,
+            PartnerConfig.CONFIG_HEADER_TEXT_SIZE,
+            PartnerConfig.CONFIG_HEADER_FONT_FAMILY,
+            PartnerStyleHelper.getLayoutGravity(header.getContext())));
+  }
+
+  /** Applies the partner style of header background to the given layout {@code headerArea}. */
+  public static void applyPartnerCustomizationHeaderAreaStyle(ViewGroup headerArea) {
+
+    if (headerArea == null) {
+      return;
+    }
+    Context context = headerArea.getContext();
+    int color =
+        PartnerConfigHelper.get(context)
+            .getColor(context, PartnerConfig.CONFIG_HEADER_AREA_BACKGROUND_COLOR);
+    headerArea.setBackgroundColor(color);
+  }
+
+  /** Applies the partner style of header icon to the given {@code iconImage}. */
+  public static void applyPartnerCustomizationIconStyle(@Nullable ImageView iconImage) {
+
+    if (iconImage == null) {
+      return;
+    }
+
+    int gravity = PartnerStyleHelper.getLayoutGravity(iconImage.getContext());
+    if (gravity != 0) {
+      setGravity(iconImage, gravity);
+    }
+  }
+
+  private static void setGravity(ImageView icon, int gravity) {
+    if (icon.getLayoutParams() instanceof LinearLayout.LayoutParams) {
+      LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) icon.getLayoutParams();
+      layoutParams.gravity = gravity;
+      icon.setLayoutParams(layoutParams);
+    }
+  }
+
+  private HeaderAreaStyler() {}
+}
diff --git a/main/src/com/google/android/setupdesign/util/Partner.java b/main/src/com/google/android/setupdesign/util/Partner.java
index 6b97bf6..aee5070 100644
--- a/main/src/com/google/android/setupdesign/util/Partner.java
+++ b/main/src/com/google/android/setupdesign/util/Partner.java
@@ -27,13 +27,17 @@
 import android.os.Build.VERSION;
 import android.os.Build.VERSION_CODES;
 import androidx.annotation.AnyRes;
+import androidx.annotation.ArrayRes;
 import androidx.annotation.ColorRes;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
 import android.util.Log;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Utilities to discover and interact with partner customizations. An overlay package is one that
@@ -55,10 +59,20 @@
   @Nullable private static Partner partner;
 
   /**
+   * Gets the string-array from partner overlay. If not available, an empty array will be returned.
+   *
+   * @see #getResourceEntry(Context, int)
+   */
+  public static Set<String> getStringArray(Context context, @ArrayRes int res) {
+    ResourceEntry resourceEntry = Partner.getResourceEntry(context, res);
+    return new HashSet<>(Arrays.asList(resourceEntry.resources.getStringArray(resourceEntry.id)));
+  }
+
+  /**
    * Gets a drawable from partner overlay, or if not available, the drawable from the original
    * context.
    *
-   * @see #getResourceEntry(android.content.Context, int)
+   * @see #getResourceEntry(Context, int)
    */
   public static Drawable getDrawable(Context context, @DrawableRes int id) {
     final ResourceEntry entry = getResourceEntry(context, id);
@@ -68,7 +82,7 @@
   /**
    * Gets a string from partner overlay, or if not available, the string from the original context.
    *
-   * @see #getResourceEntry(android.content.Context, int)
+   * @see #getResourceEntry(Context, int)
    */
   public static String getString(Context context, @StringRes int id) {
     final ResourceEntry entry = getResourceEntry(context, id);
diff --git a/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java b/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java
index c4e0162..d1d4a26 100644
--- a/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java
+++ b/main/src/com/google/android/setupdesign/util/PartnerStyleHelper.java
@@ -18,26 +18,44 @@
 
 import android.content.Context;
 import android.view.Gravity;
+import android.widget.FrameLayout;
 import com.google.android.setupcompat.partnerconfig.PartnerConfig;
 import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+import com.google.android.setupdesign.GlifLayout;
 import java.util.Locale;
 
 /** The helper reads styles from the partner configurations. */
-public class PartnerStyleHelper {
+public final class PartnerStyleHelper {
 
+  /**
+   * Returns the partner configuration of layout gravity, usually apply to wigets in header area.
+   */
   public static int getLayoutGravity(Context context) {
     String gravity =
         PartnerConfigHelper.get(context).getString(context, PartnerConfig.CONFIG_LAYOUT_GRAVITY);
-    if (gravity != null) {
-      switch (gravity.toLowerCase(Locale.ROOT)) {
-        case "center":
-          return Gravity.CENTER;
-        case "start":
-          return Gravity.START;
-        default:
-          return 0;
-      }
+    if (gravity == null) {
+      return 0;
     }
-    return 0;
+    switch (gravity.toLowerCase(Locale.ROOT)) {
+      case "center":
+        return Gravity.CENTER;
+      case "start":
+        return Gravity.START;
+      default:
+        return 0;
+    }
   }
+
+  /** Returns the given layout if apply partner heavy theme. */
+  public static boolean isPartnerHeavyThemeLayout(FrameLayout layout) {
+    if (!(layout instanceof GlifLayout)) {
+      return false;
+    }
+    if (!((GlifLayout) layout).shouldApplyPartnerHeavyThemeResource()) {
+      return false;
+    }
+    return true;
+  }
+
+  private PartnerStyleHelper() {}
 }
diff --git a/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java b/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java
new file mode 100644
index 0000000..552102e
--- /dev/null
+++ b/main/src/com/google/android/setupdesign/util/TextViewPartnerStyler.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2019 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.setupdesign.util;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.TypedValue;
+import android.widget.TextView;
+import com.google.android.setupcompat.partnerconfig.PartnerConfig;
+import com.google.android.setupcompat.partnerconfig.PartnerConfigHelper;
+
+/** Helper class to apply partner configurations to a textView. */
+final class TextViewPartnerStyler {
+
+  /** Applies given partner configurations {@code textPartnerConfigs} to the {@code textView}. */
+  public static void applyPartnerCustomizationStyle(
+      @NonNull TextView textView, @NonNull TextPartnerConfigs textPartnerConfigs) {
+
+    if (textView == null || textPartnerConfigs == null) {
+      return;
+    }
+
+    Context context = textView.getContext();
+    if (textPartnerConfigs.getTextColorConfig() != null) {
+      int textColor =
+          PartnerConfigHelper.get(context)
+              .getColor(context, textPartnerConfigs.getTextColorConfig());
+      if (textColor != 0) {
+        textView.setTextColor(textColor);
+      }
+    }
+
+    if (textPartnerConfigs.getTextLinkedColorConfig() != null) {
+      int linkTextColor =
+          PartnerConfigHelper.get(context)
+              .getColor(context, textPartnerConfigs.getTextLinkedColorConfig());
+      if (linkTextColor != 0) {
+        textView.setLinkTextColor(linkTextColor);
+      }
+    }
+
+    if (textPartnerConfigs.getTextSizeConfig() != null) {
+      float textSize =
+          PartnerConfigHelper.get(context)
+              .getDimension(context, textPartnerConfigs.getTextSizeConfig(), 0);
+      if (textSize > 0) {
+        textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+      }
+    }
+
+    if (textPartnerConfigs.getTextFontFamilyConfig() != null) {
+      String fontFamilyName =
+          PartnerConfigHelper.get(context)
+              .getString(context, textPartnerConfigs.getTextFontFamilyConfig());
+      Typeface font = Typeface.create(fontFamilyName, Typeface.NORMAL);
+      if (font != null) {
+        textView.setTypeface(font);
+      }
+    }
+
+    textView.setGravity(textPartnerConfigs.getTextGravity());
+  }
+
+  /** Keeps the partner conflagrations for a textView. */
+  public static class TextPartnerConfigs {
+    private final PartnerConfig textColorConfig;
+    private final PartnerConfig textLinkedColorConfig;
+    private final PartnerConfig textSizeConfig;
+    private final PartnerConfig textFontFamilyConfig;
+    private final int textGravity;
+
+    public TextPartnerConfigs(
+        @Nullable PartnerConfig textColorConfig,
+        @Nullable PartnerConfig textLinkedColorConfig,
+        @Nullable PartnerConfig textSizeConfig,
+        @Nullable PartnerConfig textFontFamilyConfig,
+        int textGravity) {
+      this.textColorConfig = textColorConfig;
+      this.textLinkedColorConfig = textLinkedColorConfig;
+      this.textSizeConfig = textSizeConfig;
+      this.textFontFamilyConfig = textFontFamilyConfig;
+      this.textGravity = textGravity;
+    }
+
+    public PartnerConfig getTextColorConfig() {
+      return textColorConfig;
+    }
+
+    public PartnerConfig getTextLinkedColorConfig() {
+      return textLinkedColorConfig;
+    }
+
+    public PartnerConfig getTextSizeConfig() {
+      return textSizeConfig;
+    }
+
+    public PartnerConfig getTextFontFamilyConfig() {
+      return textFontFamilyConfig;
+    }
+
+    public int getTextGravity() {
+      return textGravity;
+    }
+  }
+
+  private TextViewPartnerStyler() {}
+}
diff --git a/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java b/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java
index 0e40875..b3161fd 100644
--- a/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java
+++ b/main/src/com/google/android/setupdesign/view/HeaderRecyclerView.java
@@ -21,6 +21,7 @@
 import android.os.Build;
 import androidx.recyclerview.widget.RecyclerView;
 import android.util.AttributeSet;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -238,6 +239,107 @@
     }
   }
 
+  private boolean handleDpadDown() {
+    View focusedView = findFocus();
+    if (focusedView == null) {
+      return false;
+    }
+
+    int[] focusdLocationInWindow = new int[2];
+    int[] myLocationInWindow = new int[2];
+
+    focusedView.getLocationInWindow(focusdLocationInWindow);
+    getLocationInWindow(myLocationInWindow);
+
+    int offset =
+        (focusdLocationInWindow[1] + focusedView.getMeasuredHeight())
+            - (myLocationInWindow[1] + getMeasuredHeight());
+
+    /*
+      (focusdLocationInWindow[1] + focusedView.getMeasuredHeight())
+      is the bottom position of focused view
+
+      (myLocationInWindow[1] + getMeasuredHeight())
+      is the bottom position of recycler view
+
+      If the bottom of focused view is out of recycler view, means we need to scroll down to show
+      more detail
+
+      We scroll 70% of recycler view to make sure user can have 30% of previous information, to make
+      sure user can keep reading easily.
+    */
+    if (offset > 0) {
+      // We expect only scroll 70% of recycler view
+      int scrollLength = (int) (getMeasuredHeight() * 0.7f);
+      smoothScrollBy(0, Math.min(scrollLength, offset));
+      return true;
+    }
+
+    return false;
+  }
+
+  private boolean handleDpadUp() {
+    View focusedView = findFocus();
+    if (focusedView == null) {
+      return false;
+    }
+
+    int[] focusedLocationInWindow = new int[2];
+    int[] myLocationInWindow = new int[2];
+
+    focusedView.getLocationInWindow(focusedLocationInWindow);
+    getLocationInWindow(myLocationInWindow);
+
+    int offset = (focusedLocationInWindow[1] - myLocationInWindow[1]);
+
+    /*
+      focusedLocationInWindow[1] is top of focused view
+      myLocationInWindow[1] is top of recycler view
+
+      If top of focused view is higher than recycler view we need scroll up to show more information
+      we try to scroll up 70% of recycler view ot scroll up to the top of focused view
+    */
+    if (offset < 0) {
+      // We expect only scroll 70% of recycler view
+      int scrollLength = (int) (getMeasuredHeight() * -0.7f);
+
+      smoothScrollBy(0, Math.max(scrollLength, offset));
+      return true;
+    }
+    return false;
+  }
+
+  private boolean shouldHandleActionUp = false;
+
+  private boolean handleKeyEvent(KeyEvent keyEvent) {
+    if (shouldHandleActionUp && keyEvent.getAction() == KeyEvent.ACTION_UP) {
+      shouldHandleActionUp = false;
+      return true;
+    } else if (keyEvent.getAction() == KeyEvent.ACTION_DOWN) {
+      boolean eventHandled = false;
+      switch (keyEvent.getKeyCode()) {
+        case KeyEvent.KEYCODE_DPAD_DOWN:
+          eventHandled = handleDpadDown();
+          break;
+        case KeyEvent.KEYCODE_DPAD_UP:
+          eventHandled = handleDpadUp();
+          break;
+        default: // fall out
+      }
+      shouldHandleActionUp = eventHandled;
+      return eventHandled;
+    }
+    return false;
+  }
+
+  @Override
+  public boolean dispatchKeyEvent(KeyEvent event) {
+    if (handleKeyEvent(event)) {
+      return true;
+    }
+    return super.dispatchKeyEvent(event);
+  }
+
   /** Gets the header view of this RecyclerView, or {@code null} if there are no headers. */
   public View getHeader() {
     return header;
diff --git a/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java b/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java
index 939b8de..2e4fd71 100644
--- a/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java
+++ b/main/src/com/google/android/setupdesign/view/IllustrationVideoView.java
@@ -188,7 +188,7 @@
       mediaPlayer.setDataSource(getContext(), uri, null);
       mediaPlayer.prepareAsync();
     } catch (IOException e) {
-      Log.wtf(TAG, "Unable to set data source", e);
+      Log.e(TAG, "Unable to set video data source: " + videoRes, e);
     }
   }
 
@@ -350,7 +350,7 @@
     if (isPrepared()) {
       mp.start();
     } else {
-      Log.wtf(TAG, "Seek complete but media player not prepared");
+      Log.e(TAG, "Seek complete but media player not prepared");
     }
   }
 
@@ -368,6 +368,18 @@
     return false;
   }
 
+  /**
+   * Seeks to specified time position.
+   *
+   * @param milliseconds the offset in milliseconds from the start to seek to
+   * @throws IllegalStateException if the internal player engine has not been initialized
+   */
+  public void seekTo(int milliseconds) {
+    if (mediaPlayer != null) {
+      mediaPlayer.seekTo(milliseconds);
+    }
+  }
+
   @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
   public MediaPlayer getMediaPlayer() {
     return mediaPlayer;
diff --git a/main/src/com/google/android/setupdesign/view/RichTextView.java b/main/src/com/google/android/setupdesign/view/RichTextView.java
index c264947..338b856 100644
--- a/main/src/com/google/android/setupdesign/view/RichTextView.java
+++ b/main/src/com/google/android/setupdesign/view/RichTextView.java
@@ -32,10 +32,10 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
+import com.google.android.setupdesign.accessibility.LinkAccessibilityHelper;
 import com.google.android.setupdesign.span.LinkSpan;
 import com.google.android.setupdesign.span.LinkSpan.OnLinkClickListener;
 import com.google.android.setupdesign.span.SpanHelper;
-import com.google.android.setupdesign.util.LinkAccessibilityHelper;
 import com.google.android.setupdesign.view.TouchableMovementMethod.TouchableLinkMovementMethod;
 
 /**