Custom Theme 5/n: apply custom theme

Bug: 124796742
Change-Id: I9ec8d029c2fd1598753ea0fed471831c3cf692a6
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 990f043..0046cad 100755
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          xmlns:tools="http://schemas.android.com/tools"
           package="com.android.wallpaper">
 
     <uses-sdk android:targetSdkVersion="Q" android:minSdkVersion="28"/>
@@ -15,7 +16,9 @@
         android:requiredForAllUsers="true"
         android:restoreAnyVersion="true"
         android:supportsRtl="true"
-        android:theme="@style/CustomizationTheme">
+        android:name="com.android.customization.picker.CustomizationPickerApplication"
+        android:theme="@style/CustomizationTheme"
+        tools:replace="android:name">
         <activity
             android:name="com.android.customization.picker.CustomizationPickerActivity"
             android:label="@string/app_name"
diff --git a/res/values/override.xml b/res/values/override.xml
index 2de29c4..b3e4701 100644
--- a/res/values/override.xml
+++ b/res/values/override.xml
@@ -25,4 +25,6 @@
         corresponding to a ContentProvider in launcher to provide available grids and
         allow for changing them -->
     <string name="grid_control_metadata_name" translatable="false">com.android.launcher3.grid.control</string>
+
+    <string name="launcher_overlayable_package" translatable="false">com.android.launcher3</string>
 </resources>
\ No newline at end of file
diff --git a/src/com/android/customization/model/ResourceConstants.java b/src/com/android/customization/model/ResourceConstants.java
index f4c48c0..3b831b3 100644
--- a/src/com/android/customization/model/ResourceConstants.java
+++ b/src/com/android/customization/model/ResourceConstants.java
@@ -15,8 +15,14 @@
  */
 package com.android.customization.model;
 
+import android.content.Context;
 import android.provider.Settings.Secure;
 
+import com.android.wallpaper.R;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
 /**
  * Holds common strings used to reference system resources.
  */
@@ -37,8 +43,6 @@
      */
     String SYSUI_PACKAGE = "com.android.systemui";
 
-    String [] DEFAULT_TARGET_PACKAGES = {ANDROID_PACKAGE, SETTINGS_PACKAGE, SYSUI_PACKAGE};
-
     /**
      * Name of the system resource for icon mask
      */
@@ -53,6 +57,7 @@
     String OVERLAY_CATEGORY_ICON_ANDROID = "android.theme.customization.icon_pack.android";
     String OVERLAY_CATEGORY_ICON_SETTINGS = "android.theme.customization.icon_pack.settings";
     String OVERLAY_CATEGORY_ICON_SYSUI = "android.theme.customization.icon_pack.systemui";
+    String OVERLAY_CATEGORY_ICON_LAUNCHER = "android.theme.customization.icon_pack.launcher";
 
     /**
      * Secure Setting used to store the currently set theme.
@@ -68,4 +73,19 @@
             "ic_qs_auto_rotate",
             "ic_signal_airplane"
     };
+
+    ArrayList<String> sTargetPackages = new ArrayList<>();
+
+    static String[] getPackagesToOverlay(Context context) {
+        if (sTargetPackages.isEmpty()) {
+            sTargetPackages.addAll(Arrays.asList(ANDROID_PACKAGE, SETTINGS_PACKAGE,
+                    SYSUI_PACKAGE));
+            sTargetPackages.add(getLauncherPackage(context));
+        }
+        return sTargetPackages.toArray(new String[0]);
+    }
+
+    static String getLauncherPackage(Context context) {
+        return context.getString(R.string.launcher_overlayable_package);
+    }
 }
diff --git a/src/com/android/customization/model/theme/DefaultThemeProvider.java b/src/com/android/customization/model/theme/DefaultThemeProvider.java
index 4d3b90b..02a0047 100644
--- a/src/com/android/customization/model/theme/DefaultThemeProvider.java
+++ b/src/com/android/customization/model/theme/DefaultThemeProvider.java
@@ -18,6 +18,11 @@
 import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
 import static com.android.customization.model.ResourceConstants.CONFIG_ICON_MASK;
 import static com.android.customization.model.ResourceConstants.ICON_PREVIEW_DRAWABLE_NAME;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
 import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
 import static com.android.customization.model.ResourceConstants.SYSUI_ICONS_FOR_PREVIEW;
 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
@@ -41,13 +46,18 @@
 import com.android.customization.model.ResourcesApkProvider;
 import com.android.customization.model.theme.ThemeBundle.Builder;
 import com.android.customization.model.theme.custom.CustomTheme;
+import com.android.customization.module.CustomizationPreferences;
 import com.android.wallpaper.R;
 import com.android.wallpaper.asset.ResourceAsset;
 
 import com.bumptech.glide.request.RequestOptions;
 
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -84,10 +94,12 @@
 
     private List<ThemeBundle> mThemes;
     private Map<String, OverlayInfo> mOverlayInfos;
+    private final CustomizationPreferences mCustomizationPreferences;
 
-    public DefaultThemeProvider(Context context) {
+    public DefaultThemeProvider(Context context, CustomizationPreferences customizationPrefs) {
         super(context, context.getString(R.string.themes_stub_package));
         OverlayManager om = context.getSystemService(OverlayManager.class);
+        mCustomizationPreferences = customizationPrefs;
         mOverlayInfos = new HashMap<>();
 
         Consumer<OverlayInfo> addToMap = overlayInfo -> mOverlayInfos.put(
@@ -95,6 +107,8 @@
         om.getOverlayInfosForTarget(ANDROID_PACKAGE, UserHandle.myUserId()).forEach(addToMap);
         om.getOverlayInfosForTarget(SYSUI_PACKAGE, UserHandle.myUserId()).forEach(addToMap);
         om.getOverlayInfosForTarget(SETTINGS_PACKAGE, UserHandle.myUserId()).forEach(addToMap);
+        om.getOverlayInfosForTarget(ResourceConstants.getLauncherPackage(context),
+                UserHandle.myUserId()).forEach(addToMap);
     }
 
     @Override
@@ -127,16 +141,7 @@
 
                 String fontOverlayPackage = getOverlayPackage(FONT_PREFIX, themeName);
 
-                if (!TextUtils.isEmpty(fontOverlayPackage)) {
-                    builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage),
-                                fontOverlayPackage)
-                            .setBodyFontFamily(loadTypeface(
-                                    ResourceConstants.CONFIG_BODY_FONT_FAMILY,
-                                    fontOverlayPackage))
-                            .setHeadlineFontFamily(loadTypeface(
-                                    ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
-                                    fontOverlayPackage));
-                }
+                addFontOverlay(builder, fontOverlayPackage);
 
                 String colorOverlayPackage = getOverlayPackage(COLOR_PREFIX, themeName);
 
@@ -168,43 +173,20 @@
                 String iconAndroidOverlayPackage = getOverlayPackage(ICON_ANDROID_PREFIX,
                         themeName);
 
-                if (!TextUtils.isEmpty(iconAndroidOverlayPackage)) {
-                    builder.addOverlayPackage(getOverlayCategory(iconAndroidOverlayPackage),
-                                iconAndroidOverlayPackage)
-                            .addIcon(loadIconPreviewDrawable(
-                                    ICON_PREVIEW_DRAWABLE_NAME,
-                                    iconAndroidOverlayPackage));
-                } else {
-                    builder.addIcon(mContext.getResources().getDrawable(
-                            Resources.getSystem().getIdentifier(
-                                    ICON_PREVIEW_DRAWABLE_NAME,
-                                    "drawable", ANDROID_PACKAGE), null));
-                }
+                addAndroidIconOverlay(builder, iconAndroidOverlayPackage);
 
                 String iconSysUiOverlayPackage = getOverlayPackage(ICON_SYSUI_PREFIX, themeName);
 
-                if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) {
-                    builder.addOverlayPackage(getOverlayCategory(iconSysUiOverlayPackage),
-                            iconSysUiOverlayPackage);
-                    for (String iconName : SYSUI_ICONS_FOR_PREVIEW) {
-                        builder.addIcon(loadIconPreviewDrawable(iconName, iconSysUiOverlayPackage));
-                    }
-                }
+                addSysUiIconOverlay(builder, iconSysUiOverlayPackage);
 
                 String iconLauncherOverlayPackage = getOverlayPackage(ICON_LAUNCHER_PREFIX,
                         themeName);
-                if (!TextUtils.isEmpty(iconLauncherOverlayPackage)) {
-                    builder.addOverlayPackage(getOverlayCategory(iconLauncherOverlayPackage),
-                            iconLauncherOverlayPackage);
-                }
+                addNoPreviewIconOverlay(builder,iconLauncherOverlayPackage);
 
                 String iconSettingsOverlayPackage = getOverlayPackage(ICON_SETTINGS_PREFIX,
                         themeName);
 
-                if (!TextUtils.isEmpty(iconSettingsOverlayPackage)) {
-                    builder.addOverlayPackage(getOverlayCategory(iconSettingsOverlayPackage),
-                            iconSettingsOverlayPackage);
-                }
+                addNoPreviewIconOverlay(builder, iconSettingsOverlayPackage);
 
                 try {
                     String wallpaperResName = WALLPAPER_PREFIX + themeName;
@@ -239,6 +221,54 @@
         }
     }
 
+    private void addNoPreviewIconOverlay(Builder builder, String overlayPackage) {
+        if (!TextUtils.isEmpty(overlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(overlayPackage),
+                    overlayPackage);
+        }
+    }
+
+    private void addSysUiIconOverlay(Builder builder, String iconSysUiOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(iconSysUiOverlayPackage),
+                    iconSysUiOverlayPackage);
+            for (String iconName : SYSUI_ICONS_FOR_PREVIEW) {
+                builder.addIcon(loadIconPreviewDrawable(iconName, iconSysUiOverlayPackage));
+            }
+        }
+    }
+
+    private void addAndroidIconOverlay(Builder builder, String iconAndroidOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(iconAndroidOverlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(iconAndroidOverlayPackage),
+                        iconAndroidOverlayPackage)
+                    .addIcon(loadIconPreviewDrawable(
+                            ICON_PREVIEW_DRAWABLE_NAME,
+                            iconAndroidOverlayPackage));
+        } else {
+            builder.addIcon(mContext.getResources().getDrawable(
+                    Resources.getSystem().getIdentifier(
+                            ICON_PREVIEW_DRAWABLE_NAME,
+                            "drawable", ANDROID_PACKAGE), null));
+        }
+    }
+
+    private void addFontOverlay(Builder builder, String fontOverlayPackage)
+            throws NameNotFoundException {
+        if (!TextUtils.isEmpty(fontOverlayPackage)) {
+            builder.addOverlayPackage(getOverlayCategory(fontOverlayPackage),
+                        fontOverlayPackage)
+                    .setBodyFontFamily(loadTypeface(
+                            ResourceConstants.CONFIG_BODY_FONT_FAMILY,
+                            fontOverlayPackage))
+                    .setHeadlineFontFamily(loadTypeface(
+                            ResourceConstants.CONFIG_HEADLINE_FONT_FAMILY,
+                            fontOverlayPackage));
+        }
+    }
+
     /**
      * Default theme requires different treatment: if there are overlay packages specified in the
      * stub apk, we'll use those, otherwise we'll get the System default values. But we cannot skip
@@ -331,13 +361,7 @@
         try {
             String iconSysUiOverlayPackage = getOverlayPackage(ICON_SYSUI_PREFIX,
                     DEFAULT_THEME_NAME);
-            if (!TextUtils.isEmpty(iconSysUiOverlayPackage)) {
-                builder.addOverlayPackage(getOverlayCategory(iconSysUiOverlayPackage),
-                        iconSysUiOverlayPackage);
-                for (String iconName : SYSUI_ICONS_FOR_PREVIEW) {
-                    builder.addIcon(loadIconPreviewDrawable(iconName, iconSysUiOverlayPackage));
-                }
-            }
+            addSysUiIconOverlay(builder, iconSysUiOverlayPackage);
         } catch (NameNotFoundException | NotFoundException e) {
             Log.d(TAG, "Didn't find SystemUi icons overlay for default theme, using system default",
                     e);
@@ -377,9 +401,41 @@
         mThemes.add(builder.build());
     }
 
+    @Override
+    public void storeCustomTheme(CustomTheme theme) {
+        mCustomizationPreferences.storeCustomTheme(theme.getSerializedPackages());
+    }
+
     private void addCustomTheme() {
-        mThemes.add(new CustomTheme(mContext.getString(R.string.custom_theme_title),
-                new HashMap<>(), null));
+        String serializedTheme = mCustomizationPreferences.getSerializedCustomTheme();
+        if (TextUtils.isEmpty(serializedTheme)) {
+            mThemes.add(new CustomTheme(mContext.getString(R.string.custom_theme_title),
+                    new HashMap<>(), null));
+            return;
+        }
+        Map<String, String> customPackages = new HashMap<>();
+        try {
+            JSONObject theme = new JSONObject(serializedTheme);
+            Iterator<String> keysIterator = theme.keys();
+
+            while (keysIterator.hasNext()) {
+                String category = keysIterator.next();
+                customPackages.put(category, theme.getString(category));
+            }
+            CustomTheme.Builder builder = new CustomTheme.Builder();
+            builder.setTitle(mContext.getString(R.string.custom_theme_title));
+            addFontOverlay(builder, customPackages.get(OVERLAY_CATEGORY_FONT));
+            addAndroidIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_ANDROID));
+            addSysUiIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_SYSUI));
+            addNoPreviewIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_SETTINGS));
+            addNoPreviewIconOverlay(builder, customPackages.get(OVERLAY_CATEGORY_ICON_LAUNCHER));
+
+            mThemes.add(builder.build());
+        } catch (JSONException | NameNotFoundException | NotFoundException e) {
+            Log.w(TAG, "Couldn't read stored custom theme, resetting", e);
+            mThemes.add(new CustomTheme(mContext.getString(R.string.custom_theme_title),
+                    new HashMap<>(), null));
+        }
     }
 
     private String getOverlayPackage(String prefix, String themeName) {
diff --git a/src/com/android/customization/model/theme/OverlayManagerCompat.java b/src/com/android/customization/model/theme/OverlayManagerCompat.java
index c2e4c8e..611b23c 100644
--- a/src/com/android/customization/model/theme/OverlayManagerCompat.java
+++ b/src/com/android/customization/model/theme/OverlayManagerCompat.java
@@ -36,10 +36,12 @@
  */
 public class OverlayManagerCompat {
     private final OverlayManager mOverlayManager;
+    private final String[] mTargetPackages;
     private Map<Integer, Map<String, List<OverlayInfo>>> mOverlayByUser;
 
     public OverlayManagerCompat(Context context) {
         mOverlayManager = context.getSystemService(OverlayManager.class);
+        mTargetPackages = ResourceConstants.getPackagesToOverlay(context);
     }
 
     /**
@@ -108,7 +110,7 @@
         }
         if (!mOverlayByUser.containsKey(userId)) {
             Map<String, List<OverlayInfo>> overlaysByTarget = new HashMap<>();
-            for (String target : ResourceConstants.DEFAULT_TARGET_PACKAGES) {
+            for (String target : mTargetPackages) {
                 overlaysByTarget.put(target, getOverlayInfosForTarget(target, userId));
             }
             mOverlayByUser.put(userId, overlaysByTarget);
diff --git a/src/com/android/customization/model/theme/ThemeBundle.java b/src/com/android/customization/model/theme/ThemeBundle.java
index 5851a23..22bde72 100644
--- a/src/com/android/customization/model/theme/ThemeBundle.java
+++ b/src/com/android/customization/model/theme/ThemeBundle.java
@@ -60,7 +60,7 @@
     private final String mTitle;
     private final PreviewInfo mPreviewInfo;
     private final boolean mIsDefault;
-    private final Map<String, String> mPackagesByCategory;
+    protected final Map<String, String> mPackagesByCategory;
     @Nullable private final WallpaperInfo mWallpaperInfo;
     private WallpaperInfo mOverrideWallpaper;
 
@@ -197,7 +197,7 @@
 
     public static class Builder {
         private static final float PATH_SIZE = 100f;
-        private String mTitle;
+        protected String mTitle;
         private Typeface mBodyFontFamily;
         private Typeface mHeadlineFontFamily;
         @ColorInt private int mColorAccentLight;
@@ -209,21 +209,25 @@
         private ResourceAsset mColorPreview;
         private ResourceAsset mShapePreview;
         private WallpaperInfo mWallpaperInfo;
-        private Map<String, String> mPackages = new HashMap<>();
+        protected Map<String, String> mPackages = new HashMap<>();
 
         public ThemeBundle build() {
+            return new ThemeBundle(mTitle, mPackages, mIsDefault, mWallpaperInfo,
+                    createPreviewInfo());
+        }
+
+        protected PreviewInfo createPreviewInfo() {
             ShapeDrawable shapeDrawable = null;
             if (!TextUtils.isEmpty(mShapePath)) {
                 PathShape shape = new PathShape(PathParser.createPathFromPathData(mShapePath),
-                                PATH_SIZE, PATH_SIZE);
+                        PATH_SIZE, PATH_SIZE);
                 shapeDrawable = new ShapeDrawable(shape);
                 shapeDrawable.setIntrinsicHeight((int) PATH_SIZE);
                 shapeDrawable.setIntrinsicWidth((int) PATH_SIZE);
             }
-            return new ThemeBundle(mTitle, mPackages, mIsDefault, mWallpaperInfo,
-                    new PreviewInfo(mBodyFontFamily, mHeadlineFontFamily, mColorAccentLight,
-                            mColorAccentDark, mIcons, shapeDrawable, mWallpaperAsset,
-                            mColorPreview, mShapePreview));
+            return new PreviewInfo(mBodyFontFamily, mHeadlineFontFamily, mColorAccentLight,
+                    mColorAccentDark, mIcons, shapeDrawable, mWallpaperAsset,
+                    mColorPreview, mShapePreview);
         }
 
         public Builder setTitle(String title) {
diff --git a/src/com/android/customization/model/theme/ThemeBundleProvider.java b/src/com/android/customization/model/theme/ThemeBundleProvider.java
index dbcebef..ba4ee5d 100644
--- a/src/com/android/customization/model/theme/ThemeBundleProvider.java
+++ b/src/com/android/customization/model/theme/ThemeBundleProvider.java
@@ -16,6 +16,7 @@
 package com.android.customization.model.theme;
 
 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.theme.custom.CustomTheme;
 
 /**
  * Interface for a class that can retrieve Themes from the system.
@@ -34,4 +35,5 @@
      */
     void fetch(OptionsFetchedListener<ThemeBundle> callback, boolean reload);
 
+    void storeCustomTheme(CustomTheme theme);
 }
diff --git a/src/com/android/customization/model/theme/ThemeManager.java b/src/com/android/customization/model/theme/ThemeManager.java
index 91192dc..ee8a6ef 100644
--- a/src/com/android/customization/model/theme/ThemeManager.java
+++ b/src/com/android/customization/model/theme/ThemeManager.java
@@ -16,7 +16,13 @@
 package com.android.customization.model.theme;
 
 import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
-import static com.android.customization.model.ResourceConstants.DEFAULT_TARGET_PACKAGES;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
 import static com.android.customization.model.ResourceConstants.SETTINGS_PACKAGE;
 import static com.android.customization.model.ResourceConstants.SYSUI_PACKAGE;
 
@@ -29,6 +35,7 @@
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.ResourceConstants;
+import com.android.customization.model.theme.custom.CustomTheme;
 import com.android.wallpaper.asset.Asset;
 import com.android.wallpaper.module.WallpaperPersister;
 import com.android.wallpaper.module.WallpaperPersister.SetWallpaperCallback;
@@ -43,12 +50,13 @@
 
     private static final Set<String> THEME_CATEGORIES = new HashSet<>();
     static {
-        THEME_CATEGORIES.add(ResourceConstants.OVERLAY_CATEGORY_COLOR);
-        THEME_CATEGORIES.add(ResourceConstants.OVERLAY_CATEGORY_FONT);
-        THEME_CATEGORIES.add(ResourceConstants.OVERLAY_CATEGORY_SHAPE);
-        THEME_CATEGORIES.add(ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID);
-        THEME_CATEGORIES.add(ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS);
-        THEME_CATEGORIES.add(ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_COLOR);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_FONT);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_SHAPE);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_ANDROID);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_SETTINGS);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_SYSUI);
+        THEME_CATEGORIES.add(OVERLAY_CATEGORY_ICON_LAUNCHER);
     };
 
 
@@ -116,12 +124,14 @@
     private void applyOverlays(ThemeBundle theme, Callback callback) {
         boolean allApplied = true;
         if (theme.isDefault()) {
-            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, ResourceConstants.OVERLAY_CATEGORY_COLOR);
-            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, ResourceConstants.OVERLAY_CATEGORY_FONT);
-            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, ResourceConstants.OVERLAY_CATEGORY_SHAPE);
-            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID);
-            allApplied &= disableCurrentOverlay(SYSUI_PACKAGE, ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI);
-            allApplied &= disableCurrentOverlay(SETTINGS_PACKAGE, ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS);
+            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, OVERLAY_CATEGORY_COLOR);
+            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, OVERLAY_CATEGORY_FONT);
+            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, OVERLAY_CATEGORY_SHAPE);
+            allApplied &= disableCurrentOverlay(ANDROID_PACKAGE, OVERLAY_CATEGORY_ICON_ANDROID);
+            allApplied &= disableCurrentOverlay(SYSUI_PACKAGE, OVERLAY_CATEGORY_ICON_SYSUI);
+            allApplied &= disableCurrentOverlay(SETTINGS_PACKAGE, OVERLAY_CATEGORY_ICON_SETTINGS);
+            allApplied &= disableCurrentOverlay(ResourceConstants.getLauncherPackage(mActivity),
+                    OVERLAY_CATEGORY_ICON_LAUNCHER);
         } else {
             for (String packageName : theme.getAllPackages()) {
                 if (packageName != null) {
@@ -132,6 +142,9 @@
         }
         allApplied &= Settings.Secure.putString(mActivity.getContentResolver(),
                 ResourceConstants.THEME_SETTING, theme.getSerializedPackages());
+        if (theme instanceof CustomTheme) {
+            storeCustomTheme((CustomTheme) theme);
+        }
         mCurrentOverlays = null;
         if (allApplied) {
             callback.onSuccess();
@@ -140,6 +153,10 @@
         }
     }
 
+    private void storeCustomTheme(CustomTheme theme) {
+        mProvider.storeCustomTheme(theme);
+    }
+
     @Override
     public void fetchOptions(OptionsFetchedListener<ThemeBundle> callback) {
         mProvider.fetch(callback, false);
@@ -157,7 +174,7 @@
     public Map<String, String> getCurrentOverlays() {
         if (mCurrentOverlays == null) {
             mCurrentOverlays = mOverlayManagerCompat.getEnabledOverlaysForTargets(
-                    DEFAULT_TARGET_PACKAGES);
+                    ResourceConstants.getPackagesToOverlay(mActivity));
             mCurrentOverlays.entrySet().removeIf(
                     categoryAndPackage -> !THEME_CATEGORIES.contains(categoryAndPackage.getKey()));
         }
diff --git a/src/com/android/customization/model/theme/custom/CustomTheme.java b/src/com/android/customization/model/theme/custom/CustomTheme.java
index 982378f..aea6897 100644
--- a/src/com/android/customization/model/theme/custom/CustomTheme.java
+++ b/src/com/android/customization/model/theme/custom/CustomTheme.java
@@ -17,6 +17,9 @@
 
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
+import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.theme.ThemeBundle;
 import com.android.wallpaper.R;
 
@@ -25,7 +28,7 @@
 public class CustomTheme extends ThemeBundle {
 
     public CustomTheme(String title, Map<String, String> overlayPackages,
-            PreviewInfo previewInfo) {
+            @Nullable PreviewInfo previewInfo) {
         super(title, overlayPackages, false, null, previewInfo);
     }
 
@@ -46,7 +49,23 @@
         return false;
     }
 
+    @Override
+    public boolean isActive(CustomizationManager<ThemeBundle> manager) {
+        return isDefined() && super.isActive(manager);
+    }
+
     public boolean isDefined() {
         return getPreviewInfo() != null;
     }
+
+    Map<String, String> getPackagesByCategory() {
+        return mPackagesByCategory;
+    }
+
+    public static class Builder extends ThemeBundle.Builder {
+        @Override
+        public CustomTheme build() {
+            return new CustomTheme(mTitle, mPackages, createPreviewInfo());
+        }
+    }
 }
diff --git a/src/com/android/customization/model/theme/custom/CustomThemeManager.java b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
index 0a46ecf..1613199 100644
--- a/src/com/android/customization/model/theme/custom/CustomThemeManager.java
+++ b/src/com/android/customization/model/theme/custom/CustomThemeManager.java
@@ -15,21 +15,50 @@
  */
 package com.android.customization.model.theme.custom;
 
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
 import com.android.customization.model.CustomizationManager;
+import com.android.wallpaper.R;
+
+import java.util.HashMap;
+import java.util.Map;
 
 public class CustomThemeManager implements CustomizationManager<ThemeComponentOption> {
-    @Override
-    public boolean isAvailable() {
-        return false;
+
+    private final Map<String, String> overlayPackages = new HashMap<>();
+
+    public CustomThemeManager(@Nullable CustomTheme existingTheme) {
+        if (existingTheme != null && existingTheme.isDefined()) {
+            overlayPackages.putAll(existingTheme.getPackagesByCategory());
+        }
     }
 
     @Override
-    public void apply(ThemeComponentOption option, Callback callback) {
+    public boolean isAvailable() {
+        return true;
+    }
 
+    @Override
+    public void apply(ThemeComponentOption option, @Nullable Callback callback) {
+        overlayPackages.putAll(option.getOverlayPackages());
+        if (callback != null) {
+            callback.onSuccess();
+        }
+    }
+
+    public Map<String, String> getOverlayPackages() {
+        return overlayPackages;
+    }
+
+    public CustomTheme buildPartialCustomTheme(Context context) {
+        return new CustomTheme(context.getString(R.string.custom_theme_title),
+                overlayPackages, null);
     }
 
     @Override
     public void fetchOptions(OptionsFetchedListener<ThemeComponentOption> callback) {
-
+        //Unused
     }
 }
diff --git a/src/com/android/customization/model/theme/custom/IconOptionsProvider.java b/src/com/android/customization/model/theme/custom/IconOptionsProvider.java
index 6c85fc8..6850e90 100644
--- a/src/com/android/customization/model/theme/custom/IconOptionsProvider.java
+++ b/src/com/android/customization/model/theme/custom/IconOptionsProvider.java
@@ -16,9 +16,9 @@
 package com.android.customization.model.theme.custom;
 
 import static com.android.customization.model.ResourceConstants.ANDROID_PACKAGE;
-import static com.android.customization.model.ResourceConstants.DEFAULT_TARGET_PACKAGES;
 import static com.android.customization.model.ResourceConstants.ICON_PREVIEW_DRAWABLE_NAME;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_ANDROID;
+import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_LAUNCHER;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SETTINGS;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_ICON_SYSUI;
 import static com.android.customization.model.ResourceConstants.SYSUI_ICONS_FOR_PREVIEW;
@@ -32,6 +32,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.customization.model.ResourceConstants;
 import com.android.customization.model.theme.OverlayManagerCompat;
 import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption;
 import com.android.wallpaper.R;
@@ -51,13 +52,17 @@
 
     private final List<String> mSysUiIconsOverlayPackages = new ArrayList<>();
     private final List<String> mSettingsIconsOverlayPackages = new ArrayList<>();
+    private final List<String> mLauncherIconsOverlayPackages = new ArrayList<>();
 
     public IconOptionsProvider(Context context, OverlayManagerCompat manager) {
         super(context, manager, OVERLAY_CATEGORY_ICON_ANDROID);
+        String[] targetPackages = ResourceConstants.getPackagesToOverlay(context);
         mSysUiIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory(
-                OVERLAY_CATEGORY_ICON_SYSUI, UserHandle.myUserId(), DEFAULT_TARGET_PACKAGES));
+                OVERLAY_CATEGORY_ICON_SYSUI, UserHandle.myUserId(), targetPackages));
         mSettingsIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory(
-                OVERLAY_CATEGORY_ICON_SETTINGS, UserHandle.myUserId(), DEFAULT_TARGET_PACKAGES));
+                OVERLAY_CATEGORY_ICON_SETTINGS, UserHandle.myUserId(), targetPackages));
+        mLauncherIconsOverlayPackages.addAll(manager.getOverlayPackagesForCategory(
+                OVERLAY_CATEGORY_ICON_LAUNCHER, UserHandle.myUserId(), targetPackages));
     }
 
     @Override
@@ -93,8 +98,12 @@
             addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_SETTINGS);
         }
 
+        for (String overlayPackage : mLauncherIconsOverlayPackages) {
+            addOrUpdateOption(optionsByPrefix, overlayPackage, OVERLAY_CATEGORY_ICON_LAUNCHER);
+        }
+
         for (IconOption option : optionsByPrefix.values()) {
-            if (option.isValid()) {
+            if (option.isValid(mContext)) {
                 mOptions.add(option);
                 option.setLabel(mContext.getString(R.string.icon_component_label, mOptions.size()));
             }
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
index f973ac5..33dfc2f 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOption.java
@@ -15,12 +15,12 @@
  */
 package com.android.customization.model.theme.custom;
 
-import static com.android.customization.model.ResourceConstants.DEFAULT_TARGET_PACKAGES;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_COLOR;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_FONT;
 import static com.android.customization.model.ResourceConstants.OVERLAY_CATEGORY_SHAPE;
 import static com.android.customization.model.ResourceConstants.SYSUI_ICONS_FOR_PREVIEW;
 
+import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
@@ -32,12 +32,14 @@
 
 import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.ResourceConstants;
 import com.android.wallpaper.R;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Represents an option of a component of a custom Theme (for example, a possible color, or font,
@@ -53,7 +55,7 @@
         mOverlayPackageNames.put(category, packageName);
     }
 
-    public Map<String, String> getOverlayPackageNames() {
+    public Map<String, String> getOverlayPackages() {
         return mOverlayPackageNames;
     }
 
@@ -91,7 +93,9 @@
 
         @Override
         public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
-            return false;
+            CustomThemeManager customThemeManager = (CustomThemeManager) manager;
+            return Objects.equals(getOverlayPackages().get(OVERLAY_CATEGORY_FONT),
+                    customThemeManager.getOverlayPackages().get(OVERLAY_CATEGORY_FONT));
         }
 
         @Override
@@ -140,7 +144,14 @@
 
         @Override
         public boolean isActive(CustomizationManager<ThemeComponentOption> manager) {
-            return false;
+            CustomThemeManager customThemeManager = (CustomThemeManager) manager;
+             for (Map.Entry<String, String> overlayEntry : getOverlayPackages().entrySet()) {
+                 if(!Objects.equals(overlayEntry.getValue(),
+                         customThemeManager.getOverlayPackages().get(overlayEntry.getKey()))) {
+                     return false;
+                 }
+             }
+             return true;
         }
 
         @Override
@@ -169,8 +180,12 @@
             mIcons.add(previewIcon);
         }
 
-        public boolean isValid() {
-            return getOverlayPackageNames().keySet().size() == DEFAULT_TARGET_PACKAGES.length
+        /**
+         * @return whether this icon option has overlays and previews for all the required packages
+         */
+        public boolean isValid(Context context) {
+            return getOverlayPackages().keySet().size() ==
+                    ResourceConstants.getPackagesToOverlay(context).length
                 && mIcons.size() == SYSUI_ICONS_FOR_PREVIEW.length + 1;
         }
 
diff --git a/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java b/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java
index 316ee99..992c47c 100644
--- a/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java
+++ b/src/com/android/customization/model/theme/custom/ThemeComponentOptionProvider.java
@@ -15,14 +15,13 @@
  */
 package com.android.customization.model.theme.custom;
 
-import static com.android.customization.model.ResourceConstants.DEFAULT_TARGET_PACKAGES;
-
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.os.UserHandle;
 
 import com.android.customization.model.CustomizationManager.OptionsFetchedListener;
+import com.android.customization.model.ResourceConstants;
 import com.android.customization.model.theme.OverlayManagerCompat;
 
 import java.util.ArrayList;
@@ -44,7 +43,7 @@
         mOverlayPackages = new ArrayList<>();
         for (String category : categories) {
             mOverlayPackages.addAll(manager.getOverlayPackagesForCategory(category,
-                    UserHandle.myUserId(), DEFAULT_TARGET_PACKAGES));
+                    UserHandle.myUserId(), ResourceConstants.getPackagesToOverlay(mContext)));
         }
     }
 
diff --git a/src/com/android/customization/module/CustomizationInjector.java b/src/com/android/customization/module/CustomizationInjector.java
new file mode 100644
index 0000000..90333f0
--- /dev/null
+++ b/src/com/android/customization/module/CustomizationInjector.java
@@ -0,0 +1,25 @@
+/*
+ * 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.android.customization.module;
+
+import android.content.Context;
+
+import com.android.wallpaper.module.Injector;
+
+public interface CustomizationInjector extends Injector {
+
+    CustomizationPreferences getCustomizationPreferences(Context context);
+}
diff --git a/src/com/android/customization/module/CustomizationPreferences.java b/src/com/android/customization/module/CustomizationPreferences.java
new file mode 100644
index 0000000..2692946
--- /dev/null
+++ b/src/com/android/customization/module/CustomizationPreferences.java
@@ -0,0 +1,27 @@
+/*
+ * 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.android.customization.module;
+
+import com.android.wallpaper.module.WallpaperPreferences;
+
+public interface CustomizationPreferences extends WallpaperPreferences {
+
+    String KEY_CUSTOM_THEME= "themepicker_custom_theme";
+
+    String getSerializedCustomTheme();
+
+    void storeCustomTheme(String serializedCustomTheme);
+}
diff --git a/src/com/android/customization/module/DefaultCustomizationInjector.java b/src/com/android/customization/module/DefaultCustomizationInjector.java
new file mode 100644
index 0000000..a5a3626
--- /dev/null
+++ b/src/com/android/customization/module/DefaultCustomizationInjector.java
@@ -0,0 +1,112 @@
+/*
+ * 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.android.customization.module;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+import com.android.wallpaper.model.CategoryProvider;
+import com.android.wallpaper.model.WallpaperInfo;
+import com.android.wallpaper.module.BaseWallpaperInjector;
+import com.android.wallpaper.module.DefaultCategoryProvider;
+import com.android.wallpaper.module.LoggingOptInStatusProvider;
+import com.android.wallpaper.module.NoOpUserEventLogger;
+import com.android.wallpaper.module.UserEventLogger;
+import com.android.wallpaper.module.WallpaperPreferences;
+import com.android.wallpaper.module.WallpaperRotationRefresher;
+import com.android.wallpaper.monitor.PerformanceMonitor;
+import com.android.wallpaper.picker.PreviewFragment;
+
+public class DefaultCustomizationInjector extends BaseWallpaperInjector
+        implements CustomizationInjector {
+    private CategoryProvider mCategoryProvider;
+    private UserEventLogger mUserEventLogger;
+    private WallpaperRotationRefresher mWallpaperRotationRefresher;
+    private PerformanceMonitor mPerformanceMonitor;
+    private WallpaperPreferences mPrefs;
+
+    @Override
+    public synchronized WallpaperPreferences getPreferences(Context context) {
+        if (mPrefs == null) {
+            mPrefs = new DefaultCustomizationPreferences(context.getApplicationContext());
+        }
+        return mPrefs;
+    }
+
+
+    @Override
+    public CustomizationPreferences getCustomizationPreferences(Context context) {
+        return (CustomizationPreferences) getPreferences(context);
+    }
+
+    @Override
+    public synchronized CategoryProvider getCategoryProvider(Context context) {
+        if (mCategoryProvider == null) {
+            mCategoryProvider = new DefaultCategoryProvider(context.getApplicationContext());
+        }
+        return mCategoryProvider;
+    }
+
+    @Override
+    public synchronized UserEventLogger getUserEventLogger(Context context) {
+        if (mUserEventLogger == null) {
+            mUserEventLogger = new NoOpUserEventLogger();
+        }
+        return mUserEventLogger;
+    }
+
+    @Override
+    public synchronized WallpaperRotationRefresher getWallpaperRotationRefresher() {
+        if (mWallpaperRotationRefresher == null) {
+            mWallpaperRotationRefresher = new WallpaperRotationRefresher() {
+                @Override
+                public void refreshWallpaper(Context context, Listener listener) {
+                    // Not implemented
+                    listener.onError();
+                }
+            };
+        }
+        return mWallpaperRotationRefresher;
+    }
+
+    @Override
+    public Fragment getPreviewFragment(
+            WallpaperInfo wallpaperInfo,
+            int mode,
+            boolean testingModeEnabled) {
+        return PreviewFragment.newInstance(wallpaperInfo, mode, testingModeEnabled);
+    }
+
+    @Override
+    public synchronized PerformanceMonitor getPerformanceMonitor() {
+        if (mPerformanceMonitor == null) {
+            mPerformanceMonitor = new PerformanceMonitor() {
+                @Override
+                public void recordFullResPreviewLoadedMemorySnapshot() {
+                    // No Op
+                }
+            };
+        }
+        return mPerformanceMonitor;
+    }
+
+    @Override
+    public synchronized LoggingOptInStatusProvider getLoggingOptInStatusProvider(Context context) {
+        return null;
+    }
+
+}
diff --git a/src/com/android/customization/module/DefaultCustomizationPreferences.java b/src/com/android/customization/module/DefaultCustomizationPreferences.java
new file mode 100644
index 0000000..22220d3
--- /dev/null
+++ b/src/com/android/customization/module/DefaultCustomizationPreferences.java
@@ -0,0 +1,39 @@
+/*
+ * 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.android.customization.module;
+
+import android.content.Context;
+
+import com.android.wallpaper.module.DefaultWallpaperPreferences;
+
+public class DefaultCustomizationPreferences extends DefaultWallpaperPreferences
+        implements CustomizationPreferences {
+
+    public DefaultCustomizationPreferences(Context context) {
+        super(context);
+    }
+
+
+    @Override
+    public String getSerializedCustomTheme() {
+        return mSharedPrefs.getString(KEY_CUSTOM_THEME, null);
+    }
+
+    @Override
+    public void storeCustomTheme(String serializedCustomTheme) {
+        mSharedPrefs.edit().putString(KEY_CUSTOM_THEME, serializedCustomTheme).apply();
+    }
+}
diff --git a/src/com/android/customization/picker/CustomizationPickerActivity.java b/src/com/android/customization/picker/CustomizationPickerActivity.java
index de19aa7..70c0d82 100644
--- a/src/com/android/customization/picker/CustomizationPickerActivity.java
+++ b/src/com/android/customization/picker/CustomizationPickerActivity.java
@@ -43,6 +43,7 @@
 import com.android.customization.model.theme.OverlayManagerCompat;
 import com.android.customization.model.theme.ThemeBundle;
 import com.android.customization.model.theme.ThemeManager;
+import com.android.customization.module.CustomizationInjector;
 import com.android.customization.picker.clock.ClockFragment;
 import com.android.customization.picker.clock.ClockFragment.ClockFragmentHost;
 import com.android.customization.picker.grid.GridFragment;
@@ -156,10 +157,12 @@
             return;
         }
         //Theme
-        Injector injector = InjectorProvider.getInjector();
+        CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
         mWallpaperSetter = new WallpaperSetter(injector.getWallpaperPersister(this),
                 injector.getPreferences(this), mUserEventLogger, false);
-        ThemeManager themeManager = new ThemeManager(new DefaultThemeProvider(this), this,
+        ThemeManager themeManager = new ThemeManager(
+                new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)),
+                this,
                 mWallpaperSetter, new OverlayManagerCompat(this));
         if (themeManager.isAvailable()) {
             mSections.put(R.id.nav_theme, new ThemeSection(R.id.nav_theme, themeManager));
diff --git a/src/com/android/customization/picker/CustomizationPickerApplication.java b/src/com/android/customization/picker/CustomizationPickerApplication.java
new file mode 100644
index 0000000..79d075a
--- /dev/null
+++ b/src/com/android/customization/picker/CustomizationPickerApplication.java
@@ -0,0 +1,31 @@
+/*
+ * 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.android.customization.picker;
+
+import android.app.Application;
+
+import com.android.customization.module.DefaultCustomizationInjector;
+import com.android.wallpaper.module.InjectorProvider;
+
+public class CustomizationPickerApplication extends Application {
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        // Initialize the injector.
+        InjectorProvider.setInjector(new DefaultCustomizationInjector());
+    }
+}
diff --git a/src/com/android/customization/picker/theme/CustomThemeActivity.java b/src/com/android/customization/picker/theme/CustomThemeActivity.java
index 1b668d9..9d0c7bb 100644
--- a/src/com/android/customization/picker/theme/CustomThemeActivity.java
+++ b/src/com/android/customization/picker/theme/CustomThemeActivity.java
@@ -16,15 +16,21 @@
 package com.android.customization.picker.theme;
 
 import android.os.Bundle;
+import android.util.Log;
 import android.widget.TextView;
+import android.widget.Toast;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
 import androidx.fragment.app.FragmentTransaction;
 
+import com.android.customization.model.CustomizationManager.Callback;
+import com.android.customization.model.theme.DefaultThemeProvider;
 import com.android.customization.model.theme.OverlayManagerCompat;
+import com.android.customization.model.theme.ThemeManager;
 import com.android.customization.model.theme.custom.CustomThemeManager;
 import com.android.customization.model.theme.custom.FontOptionsProvider;
 import com.android.customization.model.theme.custom.IconOptionsProvider;
@@ -32,28 +38,39 @@
 import com.android.customization.model.theme.custom.ThemeComponentOption.FontOption;
 import com.android.customization.model.theme.custom.ThemeComponentOption.IconOption;
 import com.android.customization.model.theme.custom.ThemeComponentOptionProvider;
+import com.android.customization.module.CustomizationInjector;
 import com.android.customization.picker.theme.CustomThemeComponentFragment.CustomThemeComponentFragmentHost;
 import com.android.wallpaper.R;
-import com.android.wallpaper.module.Injector;
 import com.android.wallpaper.module.InjectorProvider;
 import com.android.wallpaper.module.UserEventLogger;
+import com.android.wallpaper.module.WallpaperSetter;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class CustomThemeActivity extends FragmentActivity implements
         CustomThemeComponentFragmentHost {
+    private static final String TAG = "CustomThemeActivity";
 
     private UserEventLogger mUserEventLogger;
     private List<ComponentStep<?>> mSteps;
     private int mCurrentStep;
+    private CustomThemeManager mCustomThemeManager;
+    private ThemeManager mThemeManager;
     private TextView mApplyButton;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Injector injector = InjectorProvider.getInjector();
+        CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
         mUserEventLogger = injector.getUserEventLogger(this);
+        mCustomThemeManager = new CustomThemeManager(null);
+        mThemeManager = new ThemeManager(
+                new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)),
+                this,
+                new WallpaperSetter(injector.getWallpaperPersister(this),
+                        injector.getPreferences(this), mUserEventLogger, false),
+                new OverlayManagerCompat(this));
         setContentView(R.layout.activity_custom_theme);
         mApplyButton = findViewById(R.id.next_button);
         mApplyButton.setOnClickListener(view -> onNextOrApply());
@@ -92,11 +109,47 @@
     }
 
     private void onNextOrApply() {
-        if (mCurrentStep < mSteps.size() - 1) {
-            // TODO: gather current step's selection
-            navigateToStep(mCurrentStep + 1);
-        }
-        // TODO: handle Apply
+        mCustomThemeManager.apply(getCurrentStepFragment().getSelectedOption(), new Callback() {
+            @Override
+            public void onSuccess() {
+                if (mCurrentStep < mSteps.size() - 1) {
+                    navigateToStep(mCurrentStep + 1);
+                } else {
+                    // We're on the last step, apply theme and leave
+                    // TODO: Verify that custom theme doesn't collide with existing one
+                    //  (compare overlay packages)
+                    mThemeManager.apply(mCustomThemeManager.buildPartialCustomTheme(
+                            CustomThemeActivity.this), new Callback() {
+                        @Override
+                        public void onSuccess() {
+                            Toast.makeText(CustomThemeActivity.this, R.string.applied_theme_msg,
+                                    Toast.LENGTH_LONG).show();
+                            finish();
+                        }
+
+                        @Override
+                        public void onError(@Nullable Throwable throwable) {
+                            Log.w(TAG, "Error applying custom theme", throwable);
+                            Toast.makeText(CustomThemeActivity.this,
+                                    R.string.apply_theme_error_msg,
+                                    Toast.LENGTH_LONG).show();
+                        }
+                    });
+                }
+            }
+
+            @Override
+            public void onError(@Nullable Throwable throwable) {
+                Log.w(TAG, "Error applying custom theme component", throwable);
+                Toast.makeText(CustomThemeActivity.this, R.string.apply_theme_error_msg,
+                        Toast.LENGTH_LONG).show();
+            }
+        });
+    }
+
+    private CustomThemeComponentFragment getCurrentStepFragment() {
+        return (CustomThemeComponentFragment)
+                getSupportFragmentManager().findFragmentById(R.id.fragment_container);
     }
 
     @Override
@@ -128,7 +181,7 @@
 
     @Override
     public CustomThemeManager getCustomThemeManager() {
-        return null;
+        return mCustomThemeManager;
     }
 
     /**
diff --git a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
index d2dcd61..eda44c9 100644
--- a/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
+++ b/src/com/android/customization/picker/theme/CustomThemeComponentFragment.java
@@ -116,6 +116,10 @@
         mHost.setCurrentStep(mPosition);
     }
 
+    public ThemeComponentOption getSelectedOption() {
+        return mSelectedOption;
+    }
+
     private void bindPreview() {
         mSelectedOption.bindPreview(mPreviewCard);
     }