Merge "Make custom style tiles border support DT" into ub-launcher3-master
diff --git a/res/layout/theme_preview_app_icon_shape.xml b/res/layout/theme_preview_app_icon_shape.xml
index af541f9..fe95f90 100644
--- a/res/layout/theme_preview_app_icon_shape.xml
+++ b/res/layout/theme_preview_app_icon_shape.xml
@@ -37,6 +37,7 @@
             android:layout_height="wrap_content"
             android:gravity="center_horizontal"
             android:orientation="vertical"
+            android:clipChildren="false"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toStartOf="@id/app_item_1"
             app:layout_constraintTop_toTopOf="parent"
@@ -62,6 +63,7 @@
             android:layout_height="wrap_content"
             android:gravity="center_horizontal"
             android:orientation="vertical"
+            android:clipChildren="false"
             app:layout_constraintStart_toEndOf="@id/app_item_0"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
@@ -98,6 +100,7 @@
             android:layout_height="wrap_content"
             android:gravity="center_horizontal"
             android:orientation="vertical"
+            android:clipChildren="false"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toStartOf="@id/app_item_3"
             app:layout_constraintTop_toTopOf="parent"
@@ -123,6 +126,7 @@
             android:layout_height="wrap_content"
             android:gravity="center_horizontal"
             android:orientation="vertical"
+            android:clipChildren="false"
             app:layout_constraintStart_toEndOf="@id/app_item_2"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toTopOf="parent"
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 437e4ea..82d02b0 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -31,6 +31,8 @@
     <color name="clockface_preview_background">@android:color/black</color>
 
     <color name="theme_preview_icon_color">@color/google_grey700</color>
+    <color name="theme_preview_workspace_shadow_color_dark">#B0000000</color>
+    <color name="theme_preview_workspace_shadow_color_transparent">@android:color/transparent</color>
 
     <color name="text_color_dark">#2d2d2d</color>
     <color name="text_color_light">@color/material_white_text</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 98ca16a..461b39e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -79,13 +79,15 @@
     <dimen name="preview_theme_smart_space_date_size">16sp</dimen>
     <dimen name="preview_theme_app_icon_size">64dp</dimen>
     <dimen name="preview_theme_app_icon_shape_text_margin_top">8dp</dimen>
-    <dimen name="preview_theme_app_icon_shape_text_size">16sp</dimen>
+    <dimen name="preview_theme_app_icon_shape_text_size">14sp</dimen>
     <dimen name="preview_theme_color_icons_padding_top">12dp</dimen>
     <dimen name="preview_theme_color_icons_padding_bottom">20dp</dimen>
     <dimen name="preview_theme_color_icons_padding_horizontal">18dp</dimen>
     <dimen name="preview_theme_color_icons_title_text_size">12sp</dimen>
     <dimen name="preview_theme_color_icons_icon_size">@dimen/preview_theme_icon_size</dimen>
     <dimen name="preview_theme_color_icons_tile_size">@dimen/preview_theme_tile_size</dimen>
+    <dimen name="preview_theme_smartspace_key_ambient_shadow_blur">1.5dp</dimen>
+    <dimen name="preview_theme_app_name_key_ambient_shadow_blur">2.5dp</dimen>
 
     <!--  For the customization previews on the picker. -->
     <dimen name="preview_content_padding_top">@dimen/preview_page_top_margin</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f1cd169..d80f74d 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -30,10 +30,6 @@
         the home screen. [CHAR LIMIT=15] -->
     <string name="grid_title">Grid</string>
 
-    <!-- Title of a section of the customization picker where the user can select a Wallpaper.
-        [CHAR LIMIT=19] -->
-    <string name="wallpaper_title">Wallpaper</string>
-
     <!-- Label for a button that allows the user to apply the currently selected Theme.
         [CHAR LIMIT=20] -->
     <string name="apply_theme_btn">Apply</string>
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 91e396c..dc93770 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -40,6 +40,7 @@
 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
 import com.android.customization.model.ResourcesApkProvider;
 import com.android.customization.model.theme.ThemeBundle.Builder;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
 import com.android.customization.model.theme.custom.CustomTheme;
 import com.android.customization.module.CustomizationPreferences;
 import com.android.wallpaper.R;
@@ -211,28 +212,25 @@
             mOverlayProvider.addSystemDefaultShape(builder);
         }
 
-        List<Drawable> icons = new ArrayList<>();
-        List<String> names = new ArrayList<>();
+        List<ShapeAppIcon> icons = new ArrayList<>();
         for (String packageName : mOverlayProvider.getShapePreviewIconPackages()) {
             Drawable icon = null;
-            String name = null;
+            CharSequence name = null;
             try {
                 icon = mContext.getPackageManager().getApplicationIcon(packageName);
                 ApplicationInfo appInfo = mContext.getPackageManager()
                         .getApplicationInfo(packageName, /* flag= */ 0);
-                name = String.valueOf(mContext.getPackageManager().getApplicationLabel(appInfo));
+                name = mContext.getPackageManager().getApplicationLabel(appInfo);
             } catch (NameNotFoundException e) {
                 Log.d(TAG, "Couldn't find app " + packageName + ", won't use it for icon shape"
                         + "preview");
             } finally {
                 if (icon != null && !TextUtils.isEmpty(name)) {
-                    icons.add(icon);
-                    names.add(name);
+                    icons.add(new ShapeAppIcon(icon, name));
                 }
             }
         }
         builder.setShapePreviewIcons(icons);
-        builder.setShapePreviewIconNames(names);
 
         try {
             String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX,
diff --git a/src/com/android/customization/model/theme/OverlayThemeExtractor.java b/src/com/android/customization/model/theme/OverlayThemeExtractor.java
index c97db5c..816176e 100644
--- a/src/com/android/customization/model/theme/OverlayThemeExtractor.java
+++ b/src/com/android/customization/model/theme/OverlayThemeExtractor.java
@@ -23,6 +23,7 @@
 
 import com.android.customization.model.ResourceConstants;
 import com.android.customization.model.theme.ThemeBundle.Builder;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
 import com.android.wallpaper.R;
 
 import java.util.ArrayList;
@@ -100,29 +101,26 @@
     }
 
     private void addShapePreviewIcons(Builder builder) {
-        List<Drawable> icons = new ArrayList<>();
-        List<String> names = new ArrayList<>();
+        List<ShapeAppIcon> icons = new ArrayList<>();
         for (String packageName : mShapePreviewIconPackages) {
             Drawable icon = null;
-            String name = null;
+            CharSequence name = null;
             try {
                 icon = mContext.getPackageManager().getApplicationIcon(packageName);
                 // Add the shape icon app name.
                 ApplicationInfo appInfo = mContext.getPackageManager()
                         .getApplicationInfo(packageName, /* flag= */ 0);
-                name = String.valueOf(mContext.getPackageManager().getApplicationLabel(appInfo));
+                name = mContext.getPackageManager().getApplicationLabel(appInfo);
             } catch (NameNotFoundException e) {
                 Log.d(TAG, "Couldn't find app " + packageName
                         + ", won't use it for icon shape preview");
             } finally {
                 if (icon != null && !TextUtils.isEmpty(name)) {
-                    icons.add(icon);
-                    names.add(name);
+                    icons.add(new ShapeAppIcon(icon, name));
                 }
             }
         }
         builder.setShapePreviewIcons(icons);
-        builder.setShapePreviewIconNames(names);
     }
 
     void addNoPreviewIconOverlay(Builder builder, String overlayPackage) {
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index f319c87..8cb2865 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -44,6 +44,7 @@
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
 import com.android.customization.widget.DynamicAdaptiveIconDrawable;
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.Asset;
@@ -250,14 +251,34 @@
         @ColorInt public final int colorAccentDark;
         public final List<Drawable> icons;
         public final Drawable shapeDrawable;
-        public final List<Drawable> shapeAppIcons;
-        public final List<String> shapeAppIconNames;
+        public final List<ShapeAppIcon> shapeAppIcons;
         @Dimension public final int bottomSheeetCornerRadius;
 
+        /** A class to represent an App icon and its name. */
+        public static class ShapeAppIcon {
+            private Drawable mIconDrawable;
+            private CharSequence mAppName;
+
+            public ShapeAppIcon(Drawable icon, CharSequence appName) {
+                mIconDrawable = icon;
+                mAppName = appName;
+            }
+
+            /** Returns the app icon drawable. */
+            public Drawable getDrawable() {
+                return mIconDrawable;
+            }
+
+            /** Returns the app name. */
+            public CharSequence getAppName() {
+                return mAppName;
+            }
+        }
+
         private PreviewInfo(Context context, Typeface bodyFontFamily, Typeface headlineFontFamily,
                 int colorAccentLight, int colorAccentDark, List<Drawable> icons,
                 Drawable shapeDrawable, @Dimension int cornerRadius,
-                List<Drawable> shapeAppIcons, List<String> shapeAppIconNames) {
+                List<ShapeAppIcon> shapeAppIcons) {
             this.bodyFontFamily = bodyFontFamily;
             this.headlineFontFamily = headlineFontFamily;
             this.colorAccentLight = colorAccentLight;
@@ -266,7 +287,6 @@
             this.shapeDrawable = shapeDrawable;
             this.bottomSheeetCornerRadius = cornerRadius;
             this.shapeAppIcons = shapeAppIcons;
-            this.shapeAppIconNames = shapeAppIconNames;
         }
 
         /**
@@ -293,8 +313,7 @@
         private boolean mIsDefault;
         @Dimension private int mCornerRadius;
         protected Map<String, String> mPackages = new HashMap<>();
-        private List<Drawable> mAppIcons = new ArrayList<>();
-        private List<String> mAppIconNames = new ArrayList<>();
+        private List<ShapeAppIcon> mAppIcons = new ArrayList<>();
 
         public ThemeBundle build(Context context) {
             return new ThemeBundle(mTitle, mPackages, mIsDefault, createPreviewInfo(context));
@@ -302,8 +321,7 @@
 
         public PreviewInfo createPreviewInfo(Context context) {
             ShapeDrawable shapeDrawable = null;
-            List<Drawable> shapeIcons = new ArrayList<>();
-            List<String> shapeIconNames = new ArrayList<>();
+            List<ShapeAppIcon> shapeIcons = new ArrayList<>();
             Path path = mShapePath;
             if (!TextUtils.isEmpty(mPathString)) {
                 path = PathParser.createPathFromPathData(mPathString);
@@ -313,25 +331,23 @@
                 shapeDrawable = new ShapeDrawable(shape);
                 shapeDrawable.setIntrinsicHeight((int) PATH_SIZE);
                 shapeDrawable.setIntrinsicWidth((int) PATH_SIZE);
-                for (int i = 0; i < mAppIcons.size(); i++) {
-                    Drawable icon = mAppIcons.get(i);
-                    String name = mAppIconNames.get(i);
-                    if (icon instanceof AdaptiveIconDrawable) {
-                        AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) icon;
-                        shapeIcons.add(new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(),
-                                adaptiveIcon.getForeground(), path));
-                        shapeIconNames.add(name);
-                    } else if (icon instanceof DynamicAdaptiveIconDrawable) {
+                for (ShapeAppIcon icon : mAppIcons) {
+                    Drawable drawable = icon.getDrawable();
+                    if (drawable instanceof AdaptiveIconDrawable) {
+                        AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) drawable;
+                        shapeIcons.add(new ShapeAppIcon(
+                                new DynamicAdaptiveIconDrawable(adaptiveIcon.getBackground(),
+                                        adaptiveIcon.getForeground(), path),
+                                icon.getAppName()));
+                    } else if (drawable instanceof DynamicAdaptiveIconDrawable) {
                         shapeIcons.add(icon);
-                        shapeIconNames.add(name);
                     }
                     // TODO: add iconloader library's legacy treatment helper methods for
                     //  non-adaptive icons
                 }
             }
             return new PreviewInfo(context, mBodyFontFamily, mHeadlineFontFamily, mColorAccentLight,
-                    mColorAccentDark, mIcons, shapeDrawable, mCornerRadius, shapeIcons,
-                    shapeIconNames);
+                    mColorAccentDark, mIcons, shapeDrawable, mCornerRadius, shapeIcons);
         }
 
         public Map<String, String> getPackages() {
@@ -392,18 +408,12 @@
             return this;
         }
 
-        public Builder setShapePreviewIcons(List<Drawable> appIcons) {
+        public Builder setShapePreviewIcons(List<ShapeAppIcon> appIcons) {
             mAppIcons.clear();
             mAppIcons.addAll(appIcons);
             return this;
         }
 
-        public Builder setShapePreviewIconNames(List<String> appIconNames) {
-            mAppIconNames.clear();
-            mAppIconNames.addAll(appIconNames);
-            return this;
-        }
-
         public Builder setBottomSheetCornerRadius(@Dimension int radius) {
             mCornerRadius = radius;
             return this;
diff --git a/src/com/android/customization/model/theme/custom/CustomThemeManager.java b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
index 7b9b67c..f4466e0 100644
--- a/src/com/android/customization/model/theme/custom/CustomThemeManager.java
+++ b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
@@ -16,20 +16,29 @@
 package com.android.customization.model.theme.custom;
 
 import android.content.Context;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
+import com.android.customization.model.theme.ThemeBundleProvider;
 import com.android.customization.model.theme.ThemeManager;
 import com.android.customization.model.theme.custom.CustomTheme.Builder;
 
+import org.json.JSONException;
+
 import java.util.Map;
 
 public class CustomThemeManager implements CustomizationManager<ThemeComponentOption> {
 
+    private static final String TAG = "CustomThemeManager";
+    private static final String EXTRA_CUSTOM_THEME_OPTION = "custom_theme_option";
+
     private final CustomTheme mOriginalTheme;
-    private final CustomTheme.Builder mBuilder;
+    private CustomTheme.Builder mBuilder;
 
     private CustomThemeManager(Map<String, String> overlayPackages,
             @Nullable CustomTheme originalTheme) {
@@ -72,6 +81,29 @@
         return mBuilder.createPreviewInfo(context);
     }
 
+    /** Saves the custom theme selections while system config changes. */
+    public void saveCustomTheme(Context context, Bundle savedInstanceState) {
+        CustomTheme customTheme =
+                buildPartialCustomTheme(context, /* id= */ null, /* title= */ null);
+        savedInstanceState.putString(EXTRA_CUSTOM_THEME_OPTION,
+                customTheme.getSerializedPackages());
+    }
+
+    /** Reads the saved custom theme after system config changed. */
+    public void readCustomTheme(ThemeBundleProvider themeBundleProvider,
+                                Bundle savedInstanceState) {
+        String packages = savedInstanceState.getString(EXTRA_CUSTOM_THEME_OPTION);
+        if (!TextUtils.isEmpty(packages)) {
+            try {
+                mBuilder = themeBundleProvider.parseCustomTheme(packages);
+            } catch (JSONException e) {
+                Log.w(TAG, "Couldn't parse provided custom theme.");
+            }
+        } else {
+            Log.w(TAG, "No custom theme being restored.");
+        }
+    }
+
     public static CustomThemeManager create(
             @Nullable CustomTheme customTheme, ThemeManager themeManager) {
         if (customTheme != null && customTheme.isDefined()) {
diff --git a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
index c10a5b2..f93b892 100644
--- a/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
+++ b/src/com/android/customization/model/theme/custom/ShapeOptionsProvider.java
@@ -37,10 +37,10 @@
 
 import androidx.annotation.Dimension;
 import androidx.core.graphics.PathParser;
-import androidx.core.util.Pair;
 
 import com.android.customization.model.ResourceConstants;
 import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
 import com.android.customization.model.theme.custom.ThemeComponentOption.ShapeOption;
 import com.android.customization.widget.DynamicAdaptiveIconDrawable;
 import com.android.wallpaper.R;
@@ -73,14 +73,11 @@
             try {
                 Path path = loadPath(mContext.getPackageManager()
                         .getResourcesForApplication(overlayPackage), overlayPackage);
-                ShapeDrawable shapeDrawable = createShapeDrawable(path);
                 PackageManager pm = mContext.getPackageManager();
                 String label = pm.getApplicationInfo(overlayPackage, 0).loadLabel(pm).toString();
-                Pair<List<Drawable>, List<String>> shapedIconsAndNames =
-                        getShapedIconsAndNames(path);
                 mOptions.add(new ShapeOption(overlayPackage, label, path,
-                        loadCornerRadius(overlayPackage), shapeDrawable, shapedIconsAndNames.first,
-                        shapedIconsAndNames.second));
+                        loadCornerRadius(overlayPackage), createShapeDrawable(path),
+                        getShapedAppIcons(path)));
             } catch (NameNotFoundException | NotFoundException e) {
                 Log.w(TAG, String.format("Couldn't load shape overlay %s, will skip it",
                         overlayPackage), e);
@@ -91,13 +88,11 @@
     private void addDefault() {
         Resources system = Resources.getSystem();
         Path path = loadPath(system, ANDROID_PACKAGE);
-        ShapeDrawable shapeDrawable = createShapeDrawable(path);
-        Pair<List<Drawable>, List<String>> shapedIconsAndNames = getShapedIconsAndNames(path);
         mOptions.add(new ShapeOption(null, mContext.getString(R.string.default_theme_title), path,
                 system.getDimensionPixelOffset(
-                    system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS,
-                        "dimen", ResourceConstants.ANDROID_PACKAGE)),
-                shapeDrawable, shapedIconsAndNames.first, shapedIconsAndNames.second));
+                        system.getIdentifier(ResourceConstants.CONFIG_CORNERRADIUS,
+                                "dimen", ResourceConstants.ANDROID_PACKAGE)),
+                createShapeDrawable(path), getShapedAppIcons(path)));
     }
 
     private ShapeDrawable createShapeDrawable(Path path) {
@@ -108,12 +103,11 @@
         return shapeDrawable;
     }
 
-    private Pair<List<Drawable>, List<String>> getShapedIconsAndNames(Path path) {
-        List<Drawable> icons = new ArrayList<>();
-        List<String> names = new ArrayList<>();
+    private List<ShapeAppIcon> getShapedAppIcons(Path path) {
+        List<ShapeAppIcon> shapedAppIcons = new ArrayList<>();
         for (String packageName : mShapePreviewIconPackages) {
             Drawable icon = null;
-            String name = null;
+            CharSequence name = null;
             try {
                 Drawable appIcon = mContext.getPackageManager().getApplicationIcon(packageName);
                 if (appIcon instanceof AdaptiveIconDrawable) {
@@ -123,20 +117,18 @@
 
                     ApplicationInfo appInfo = mContext.getPackageManager()
                             .getApplicationInfo(packageName, /* flag= */ 0);
-                    name = String.valueOf(
-                            mContext.getPackageManager().getApplicationLabel(appInfo));
+                    name = mContext.getPackageManager().getApplicationLabel(appInfo);
                 }
             } catch (NameNotFoundException e) {
                 Log.d(TAG, "Couldn't find app " + packageName
                         + ", won't use it for icon shape preview");
             } finally {
                 if (icon != null && !TextUtils.isEmpty(name)) {
-                    icons.add(icon);
-                    names.add(name);
+                    shapedAppIcons.add(new ShapeAppIcon(icon, name));
                 }
             }
         }
-        return Pair.create(icons, names);
+        return shapedAppIcons;
     }
 
     private Path loadPath(Resources overlayRes, String packageName) {
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
index fed641f..b3d9d15 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -55,6 +55,7 @@
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
 import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
 import com.android.customization.model.theme.custom.CustomTheme.Builder;
 import com.android.wallpaper.R;
 
@@ -416,8 +417,7 @@
     public static class ShapeOption extends ThemeComponentOption {
 
         private final LayerDrawable mShape;
-        private final List<Drawable> mAppIcons;
-        private final List<String> mAppIconNames;
+        private final List<ShapeAppIcon> mAppIcons;
         private final String mLabel;
         private final Path mPath;
         private final int mCornerRadius;
@@ -428,11 +428,10 @@
 
         ShapeOption(String packageName, String label, Path path,
                 @Dimension int cornerRadius, Drawable shapeDrawable,
-                List<Drawable> appIcons, List<String> appIconNames) {
+                List<ShapeAppIcon> appIcons) {
             addOverlayPackage(OVERLAY_CATEGORY_SHAPE, packageName);
             mLabel = label;
             mAppIcons = appIcons;
-            mAppIconNames = appIconNames;
             mPath = path;
             mCornerRadius = cornerRadius;
             Drawable background = shapeDrawable.getConstantState().newDrawable();
@@ -491,7 +490,7 @@
             }
             for (int i = 0; i < mShapeIconIds.length && i < mAppIcons.size(); i++) {
                 ImageView iconView = cardBody.findViewById(mShapeIconIds[i]);
-                iconView.setBackground(mAppIcons.get(i));
+                iconView.setBackground(mAppIcons.get(i).getDrawable());
             }
         }
 
@@ -499,8 +498,7 @@
         public Builder buildStep(Builder builder) {
             builder.setShapePath(mPath)
                     .setBottomSheetCornerRadius(mCornerRadius)
-                    .setShapePreviewIcons(mAppIcons)
-                    .setShapePreviewIconNames(mAppIconNames);
+                    .setShapePreviewIcons(mAppIcons);
             return super.buildStep(builder);
         }
     }
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index f9cca14..fc56337 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -85,12 +85,12 @@
     protected void onCreate(Bundle savedInstanceState) {
         CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
         mUserEventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(this);
+        ThemeBundleProvider themeProvider =
+                new DefaultThemeProvider(this, injector.getCustomizationPreferences(this));
         Intent intent = getIntent();
         CustomTheme customTheme = null;
         if (intent != null && intent.hasExtra(EXTRA_THEME_PACKAGES)
                 && intent.hasExtra(EXTRA_THEME_TITLE) && intent.hasExtra(EXTRA_THEME_ID)) {
-            ThemeBundleProvider themeProvider =
-                    new DefaultThemeProvider(this, injector.getCustomizationPreferences(this));
             mIsDefinedTheme = intent.getBooleanExtra(CREATE_NEW_THEME, true);
             try {
                 CustomTheme.Builder themeBuilder = themeProvider.parseCustomTheme(
@@ -112,6 +112,9 @@
                 mUserEventLogger);
         mThemeManager.fetchOptions(null, false);
         mCustomThemeManager = CustomThemeManager.create(customTheme, mThemeManager);
+        if (savedInstanceState != null) {
+            mCustomThemeManager.readCustomTheme(themeProvider, savedInstanceState);
+        }
 
         int currentStep = 0;
         if (savedInstanceState != null) {
@@ -138,6 +141,9 @@
     protected void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         outState.putInt(KEY_STATE_CURRENT_STEP, mCurrentStep);
+        if (mCustomThemeManager != null) {
+            mCustomThemeManager.saveCustomTheme(this, outState);
+        }
     }
 
     private void navigateToStep(int i) {
diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
index b2217aa..a6fdb1c 100644
--- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
+++ b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
@@ -98,6 +98,9 @@
             mOptionsController.addListener(selected -> {
                 mSelectedOption = (ThemeComponentOption) selected;
                 bindPreview();
+                // Preview and apply. The selection will be kept whatever user goes to previous page
+                // or encounter system config changes, the current selection can be recovered.
+                mCustomThemeManager.apply(mSelectedOption, /* callback= */ null);
             });
             mOptionsController.initOptions(mCustomThemeManager);
 
diff --git a/src/com/android/customization/picker/theme/ThemeFragment.java b/src/com/android/customization/picker/theme/ThemeFragment.java
index 1f4917e..1c3b4c1 100644
--- a/src/com/android/customization/picker/theme/ThemeFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFragment.java
@@ -290,28 +290,35 @@
                     }
                 });
                 mOptionsController.initOptions(mThemeManager);
+
                 String previouslySelected = savedInstanceState != null
                         ? savedInstanceState.getString(KEY_SELECTED_THEME) : null;
+                ThemeBundle previouslySelectedTheme = null;
+                ThemeBundle activeTheme = null;
                 for (ThemeBundle theme : options) {
                     if (previouslySelected != null
                             && previouslySelected.equals(theme.getSerializedPackages())) {
-                        mSelectedTheme = theme;
-                    } else if (theme.isActive(mThemeManager)) {
-                        mSelectedTheme = theme;
-                        break;
+                        previouslySelectedTheme = theme;
+                    }
+                    if (theme.isActive(mThemeManager)) {
+                        activeTheme = theme;
                     }
                 }
+                mSelectedTheme = previouslySelectedTheme != null
+                        ? previouslySelectedTheme
+                        : activeTheme;
+
                 if (mSelectedTheme == null) {
                     // Select the default theme if there is no matching custom enabled theme
                     mSelectedTheme = findDefaultThemeBundle(options);
-                } else {
-                    // Only show show checkmark if we found a matching theme
-                    mOptionsController.setAppliedOption(mSelectedTheme);
                 }
                 mOptionsController.setSelectedOption(mSelectedTheme);
                 // Set selected option above will show BottomActionBar when entering the tab. But
-                // it should not show when entering the tab.
-                mBottomActionBar.hide();
+                // it should not show when entering the tab. But it's visible for previously
+                // selected theme.
+                if (mSelectedTheme != previouslySelectedTheme) {
+                    mBottomActionBar.hide();
+                }
             }
             @Override
             public void onError(@Nullable Throwable throwable) {
@@ -335,9 +342,6 @@
             if (mSelectedTheme == null) {
                 // Select the default theme if there is no matching custom enabled theme
                 mSelectedTheme = findDefaultThemeBundle(options);
-            } else {
-                // Only show show checkmark if we found a matching theme
-                mOptionsController.setAppliedOption(mSelectedTheme);
             }
             mOptionsController.setSelectedOption(mSelectedTheme);
             // Set selected option above will show BottomActionBar,
diff --git a/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java b/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java
index 473f480..253720e 100644
--- a/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java
+++ b/src/com/android/customization/picker/theme/ThemeFullPreviewFragment.java
@@ -125,11 +125,12 @@
             public void onLayoutChange(View v, int left, int top, int right, int bottom,
                                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 wallpaperPreviewer.updatePreviewCardRadius();
+                // Let's use half size of full preview card to reduce memory and loading time.
                 WallpaperColorsLoader.getWallpaperColors(
                         getContext(),
                         mWallpaper.getThumbAsset(getContext()),
-                        wallpaperImageView.getMeasuredWidth(),
-                        wallpaperImageView.getMeasuredHeight(),
+                        wallpaperImageView.getMeasuredWidth() / 2,
+                        wallpaperImageView.getMeasuredHeight() / 2,
                         themeOptionPreviewer::updateColorForLauncherWidgets);
                 view.removeOnLayoutChangeListener(this);
             }
diff --git a/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java b/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java
index a3850c6..f24cf64 100644
--- a/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java
+++ b/src/com/android/customization/picker/theme/ThemeOptionPreviewer.java
@@ -31,6 +31,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.Switch;
@@ -44,6 +45,7 @@
 
 import com.android.customization.model.theme.ThemeBundle;
 import com.android.customization.model.theme.ThemeBundle.PreviewInfo;
+import com.android.customization.model.theme.ThemeBundle.PreviewInfo.ShapeAppIcon;
 import com.android.wallpaper.R;
 import com.android.wallpaper.util.ScreenSizeCalculator;
 import com.android.wallpaper.util.TimeUtils;
@@ -102,12 +104,16 @@
     private TextView mSmartSpaceDate;
     private TimeTicker mTicker;
 
+    private boolean mHasPreviewInfoSet;
+    private boolean mHasWallpaperColorSet;
+
     ThemeOptionPreviewer(Lifecycle lifecycle, Context context, ViewGroup previewContainer) {
         lifecycle.addObserver(this);
 
         mContext = context;
         mContentView = LayoutInflater.from(context).inflate(
                 R.layout.theme_preview_content, /* root= */ null);
+        mContentView.setVisibility(View.INVISIBLE);
         mStatusBarClock = mContentView.findViewById(R.id.theme_preview_clock);
         mSmartSpaceDate = mContentView.findViewById(R.id.smart_space_date);
         updateTime();
@@ -155,11 +161,12 @@
         setHeadlineFont(previewInfo.headlineFontFamily);
         setTopBarIcons(previewInfo.icons);
         setAppIconShape(previewInfo.shapeAppIcons);
-        setAppIconName(previewInfo.shapeAppIconNames);
         setColorAndIconsSection(previewInfo.icons, previewInfo.shapeDrawable,
                 previewInfo.resolveAccentColor(mContext.getResources()));
         setColorAndIconsBoxRadius(previewInfo.bottomSheeetCornerRadius);
         setQsbRadius(previewInfo.bottomSheeetCornerRadius);
+        mHasPreviewInfoSet = true;
+        showPreviewIfHasAllConfigSet();
     }
 
     /**
@@ -167,24 +174,45 @@
      * text) which will change its content color according to different wallpapers.
      */
     public void updateColorForLauncherWidgets(WallpaperColors colors) {
-        int color = mContext.getColor(
-                (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0
-                        ? R.color.text_color_light
-                        : R.color.text_color_dark);
+        boolean useLightTextColor =
+                (colors.getColorHints() & WallpaperColors.HINT_SUPPORTS_DARK_TEXT) == 0;
+        int textColor = mContext.getColor(useLightTextColor
+                ? R.color.text_color_light
+                : R.color.text_color_dark);
+        int textShadowColor = mContext.getColor(useLightTextColor
+                ? R.color.theme_preview_workspace_shadow_color_dark
+                : R.color.theme_preview_workspace_shadow_color_transparent);
         // Update the top status bar clock text color.
-        mStatusBarClock.setTextColor(color);
+        mStatusBarClock.setTextColor(textColor);
         // Update the top status bar icon color.
         ViewGroup iconsContainer = mContentView.findViewById(R.id.theme_preview_top_bar_icons);
         for (int i = 0; i < iconsContainer.getChildCount(); i++) {
             ((ImageView) iconsContainer.getChildAt(i))
-                    .setImageTintList(ColorStateList.valueOf(color));
+                    .setImageTintList(ColorStateList.valueOf(textColor));
         }
         // Update smart space date color.
-        ((TextView) mContentView.findViewById(R.id.smart_space_date)).setTextColor(color);
+        mSmartSpaceDate.setTextColor(textColor);
+        mSmartSpaceDate.setShadowLayer(
+                mContext.getResources().getDimension(
+                        R.dimen.preview_theme_smartspace_key_ambient_shadow_blur),
+                /* dx = */ 0,
+                /* dy = */ 0,
+                textShadowColor);
+
         // Update shape app icon name text color.
         for (int id : mShapeIconAppNameIds) {
-            ((TextView) mContentView.findViewById(id)).setTextColor(color);
+            TextView appName = mContentView.findViewById(id);
+            appName.setTextColor(textColor);
+            appName.setShadowLayer(
+                    mContext.getResources().getDimension(
+                            R.dimen.preview_theme_app_name_key_ambient_shadow_blur),
+                    /* dx = */ 0,
+                    /* dy = */ 0,
+                    textShadowColor);
         }
+
+        mHasWallpaperColorSet = true;
+        showPreviewIfHasAllConfigSet();
     }
 
     @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
@@ -202,6 +230,20 @@
         }
     }
 
+    private void showPreviewIfHasAllConfigSet() {
+        if (mHasPreviewInfoSet && mHasWallpaperColorSet
+                && mContentView.getVisibility() != View.VISIBLE) {
+            mContentView.setAlpha(0f);
+            mContentView.setVisibility(View.VISIBLE);
+            mContentView.animate().alpha(1f)
+                    .setStartDelay(50)
+                    .setDuration(200)
+                    .setInterpolator(AnimationUtils.loadInterpolator(mContext,
+                            android.R.interpolator.fast_out_linear_in))
+                    .start();
+        }
+    }
+
     private void setHeadlineFont(Typeface headlineFont) {
         mStatusBarClock.setTypeface(headlineFont);
         mSmartSpaceDate.setTypeface(headlineFont);
@@ -231,17 +273,16 @@
         }
     }
 
-    private void setAppIconShape(List<Drawable> appIcons) {
-        for (int i = 0; i < mShapeAppIconIds.length && i < appIcons.size(); i++) {
+    private void setAppIconShape(List<ShapeAppIcon> appIcons) {
+        for (int i = 0; i < mShapeAppIconIds.length && i < mShapeIconAppNameIds.length
+                && i < appIcons.size(); i++) {
+            ShapeAppIcon icon = appIcons.get(i);
+            // Set app icon.
             ImageView iconView = mContentView.findViewById(mShapeAppIconIds[i]);
-            iconView.setBackground(appIcons.get(i));
-        }
-    }
-
-    private void setAppIconName(List<String> appIconNames) {
-        for (int i = 0; i < mShapeIconAppNameIds.length && i < appIconNames.size(); i++) {
+            iconView.setBackground(icon.getDrawable());
+            // Set app name.
             TextView appName = mContentView.findViewById(mShapeIconAppNameIds[i]);
-            appName.setText(appIconNames.get(i));
+            appName.setText(icon.getAppName());
         }
     }
 
diff --git a/src/com/android/customization/widget/ThemeInfoView.java b/src/com/android/customization/widget/ThemeInfoView.java
index 5b05484..a733a40 100644
--- a/src/com/android/customization/widget/ThemeInfoView.java
+++ b/src/com/android/customization/widget/ThemeInfoView.java
@@ -72,7 +72,7 @@
 
             if (previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX) != null) {
                 mAppPreviewImageView.setBackground(
-                        previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX));
+                        previewInfo.shapeAppIcons.get(SHAPE_PREVIEW_INDEX).getDrawable());
             }
 
             if (previewInfo.shapeDrawable != null) {