Add infrastructure for extra customization sections

Bug: 175869253
Test: Build WPPG and install to run
Change-Id: Ib0c1edb0e1b0529bd3326f8d4df502f4dfc56b76
diff --git a/src/com/android/customization/model/CustomizationManager.java b/src/com/android/customization/model/CustomizationManager.java
index 7b9f463..efc4c74 100644
--- a/src/com/android/customization/model/CustomizationManager.java
+++ b/src/com/android/customization/model/CustomizationManager.java
@@ -16,7 +16,6 @@
 package com.android.customization.model;
 
 import android.util.Log;
-import android.widget.Toast;
 
 import androidx.annotation.Nullable;
 
@@ -29,6 +28,20 @@
 public interface CustomizationManager<T extends CustomizationOption> {
 
     /**
+     * Create a new {@link CustomizationSection} corresponding to this Manager
+     */
+    default CustomizationSection<T> createSection() {
+        return null;
+    }
+
+    /**
+     * @return the id in the navigation menu for the section this Manager manages.
+     */
+    default int getNavId() {
+        return 0;
+    };
+
+    /**
      * Callback for applying a customization option.
      */
     interface Callback {
diff --git a/src/com/android/customization/model/CustomizationSection.java b/src/com/android/customization/model/CustomizationSection.java
new file mode 100644
index 0000000..da1b4f8
--- /dev/null
+++ b/src/com/android/customization/model/CustomizationSection.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.customization.model;
+
+import android.content.Context;
+
+import androidx.annotation.IdRes;
+import androidx.fragment.app.Fragment;
+
+/**
+ * Represents a section of the Picker (eg "ThemeBundle", "Clock", etc).
+ * There should be a concrete subclass per available section, providing the corresponding
+ * Fragment to be displayed when switching to each section.
+ * @param <T> CustomizationOption that this section represents.
+ */
+public abstract class CustomizationSection<T extends CustomizationOption> {
+
+    /**
+     * IdRes used to identify this section in the BottomNavigationView menu.
+     */
+    @IdRes
+    public final int id;
+    protected final CustomizationManager<T> mCustomizationManager;
+
+    public CustomizationSection(@IdRes int id, CustomizationManager<T> manager) {
+        this.id = id;
+        mCustomizationManager = manager;
+    }
+
+    /**
+     * @return the Fragment corresponding to this section.
+     */
+    public abstract Fragment getFragment(Context c);
+
+    public CustomizationManager<T> getCustomizationManager() {
+        return mCustomizationManager;
+    }
+
+}
diff --git a/src/com/android/customization/module/CustomizationInjector.java b/src/com/android/customization/module/CustomizationInjector.java
index 85853de..913b686 100644
--- a/src/com/android/customization/module/CustomizationInjector.java
+++ b/src/com/android/customization/module/CustomizationInjector.java
@@ -19,6 +19,7 @@
 
 import androidx.fragment.app.FragmentActivity;
 
+import com.android.customization.model.CustomizationManager;
 import com.android.customization.model.theme.OverlayManagerCompat;
 import com.android.customization.model.theme.ThemeBundleProvider;
 import com.android.customization.model.theme.ThemeManager;
@@ -30,4 +31,12 @@
 
     ThemeManager getThemeManager(ThemeBundleProvider provider, FragmentActivity activity,
             OverlayManagerCompat overlayManagerCompat, ThemesUserEventLogger logger);
+
+    /**
+     * Obtain an extra CustomizationManager to add to the bottom nav
+     */
+    default CustomizationManager<?> getExtraManager(FragmentActivity activity,
+            OverlayManagerCompat overlayManagerCompat, ThemesUserEventLogger eventLogger) {
+        return null;
+    }
 }
diff --git a/src/com/android/customization/picker/CustomizationFragmentHost.java b/src/com/android/customization/picker/CustomizationFragmentHost.java
new file mode 100644
index 0000000..d700ec6
--- /dev/null
+++ b/src/com/android/customization/picker/CustomizationFragmentHost.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.customization.picker;
+
+import com.android.customization.model.CustomizationManager;
+
+/**
+ * Interface to be implemented by an Activity hosting any Customization Fragment
+ */
+public interface CustomizationFragmentHost {
+
+    /**
+     * Gets the CustomizationManager manager for the given section
+     * @return the {@link CustomizationManager}
+     */
+    CustomizationManager<?> getCustomizationManager(int sectionId);
+}
diff --git a/src/com/android/customization/picker/CustomizationPickerActivity.java b/src/com/android/customization/picker/CustomizationPickerActivity.java
index 45f4cf4..d28ee66 100644
--- a/src/com/android/customization/picker/CustomizationPickerActivity.java
+++ b/src/com/android/customization/picker/CustomizationPickerActivity.java
@@ -16,6 +16,7 @@
 package com.android.customization.picker;
 
 import android.app.Activity;
+import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
@@ -37,7 +38,7 @@
 import androidx.fragment.app.FragmentTransaction;
 
 import com.android.customization.model.CustomizationManager;
-import com.android.customization.model.CustomizationOption;
+import com.android.customization.model.CustomizationSection;
 import com.android.customization.model.clock.ClockManager;
 import com.android.customization.model.clock.Clockface;
 import com.android.customization.model.clock.ContentProviderClockProvider;
@@ -90,8 +91,9 @@
  *  Fragments providing customization options.
  */
 public class CustomizationPickerActivity extends FragmentActivity implements WallpapersUiContainer,
-        CategoryFragmentHost, ThemeFragmentHost, GridFragmentHost, ClockFragmentHost,
-        BottomActionBarHost, FragmentTransactionChecker {
+        CategoryFragmentHost, CustomizationFragmentHost,
+        ThemeFragmentHost, GridFragmentHost,
+        ClockFragmentHost, BottomActionBarHost, FragmentTransactionChecker {
 
     public static final String WALLPAPER_FLAVOR_EXTRA =
             "com.android.launcher3.WALLPAPER_FLAVOR";
@@ -180,7 +182,6 @@
         // Keep CategoryFragment's design to load category within its fragment
         if (section instanceof WallpaperSection) {
             switchFragment(section);
-            section.onVisible();
         }
     }
 
@@ -232,6 +233,11 @@
         CustomizationInjector injector = (CustomizationInjector) InjectorProvider.getInjector();
         ThemesUserEventLogger eventLogger = (ThemesUserEventLogger) injector.getUserEventLogger(
                 this);
+        CustomizationManager<?> extraManager = injector.getExtraManager(
+                this, new OverlayManagerCompat(this), eventLogger);
+        if (extraManager != null && extraManager.isAvailable()) {
+            mSections.put(extraManager.getNavId(), extraManager.createSection());
+        }
         ThemeManager themeManager = injector.getThemeManager(
                 new DefaultThemeProvider(this, injector.getCustomizationPreferences(this)),
                 this, new OverlayManagerCompat(this), eventLogger);
@@ -280,7 +286,6 @@
             int id = item.getItemId();
             CustomizationSection section = mSections.get(id);
             switchFragment(section);
-            section.onVisible();
             String name = getResources().getResourceName(id);
             if (!prefs.getTabVisited(name)) {
                 prefs.setTabVisited(name);
@@ -357,7 +362,7 @@
     private void switchFragment(CustomizationSection section) {
         final FragmentManager fragmentManager = getSupportFragmentManager();
 
-        Fragment fragment = section.getFragment();
+        Fragment fragment = section.getFragment(this);
 
         final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
         fragmentTransaction.replace(R.id.fragment_container, fragment);
@@ -427,22 +432,29 @@
         return mDelegate;
     }
 
+
+    @Override
+    public CustomizationManager<?> getCustomizationManager(int id) {
+        CustomizationSection<?> section = mSections.get(id);
+        return section == null ? null : section.getCustomizationManager();
+    }
+
     @Override
     public ClockManager getClockManager() {
         CustomizationSection section = mSections.get(R.id.nav_clock);
-        return section == null ? null : (ClockManager) section.customizationManager;
+        return section == null ? null : (ClockManager) section.getCustomizationManager();
     }
 
     @Override
     public GridOptionsManager getGridOptionsManager() {
         CustomizationSection section = mSections.get(R.id.nav_grid);
-        return section == null ? null : (GridOptionsManager) section.customizationManager;
+        return section == null ? null : (GridOptionsManager) section.getCustomizationManager();
     }
 
     @Override
     public ThemeManager getThemeManager() {
         CustomizationSection section = mSections.get(R.id.nav_theme);
-        return section == null ? null : (ThemeManager) section.customizationManager;
+        return section == null ? null : (ThemeManager) section.getCustomizationManager();
     }
 
     @Override
@@ -476,32 +488,6 @@
     }
 
     /**
-     * Represents a section of the Picker (eg "ThemeBundle", "Clock", etc).
-     * There should be a concrete subclass per available section, providing the corresponding
-     * Fragment to be displayed when switching to each section.
-     */
-    static abstract class CustomizationSection<T extends CustomizationOption> {
-
-        /**
-         * IdRes used to identify this section in the BottomNavigationView menu.
-         */
-        @IdRes final int id;
-        protected final CustomizationManager<T> customizationManager;
-
-        private CustomizationSection(@IdRes int id, CustomizationManager<T> manager) {
-            this.id = id;
-            this.customizationManager = manager;
-        }
-
-        /**
-         * @return the Fragment corresponding to this section.
-         */
-        abstract Fragment getFragment();
-
-        void onVisible() {}
-    }
-
-    /**
      * {@link CustomizationSection} corresponding to the "Wallpaper" section of the Picker.
      */
     private class WallpaperSection extends CustomizationSection {
@@ -511,7 +497,7 @@
         }
 
         @Override
-        Fragment getFragment() {
+        public Fragment getFragment(Context c) {
             if (mWallpaperCategoryFragment == null) {
                 mWallpaperCategoryFragment = CategoryFragment.newInstance(
                         getString(R.string.wallpaper_title));
@@ -529,7 +515,7 @@
         }
 
         @Override
-        Fragment getFragment() {
+        public Fragment getFragment(Context c) {
             if (mFragment == null) {
                 mFragment = ThemeFragment.newInstance(getString(R.string.theme_title));
             }
@@ -546,7 +532,7 @@
         }
 
         @Override
-        Fragment getFragment() {
+        public Fragment getFragment(Context c) {
             if (mFragment == null) {
                 mFragment = GridFragment.newInstance(getString(R.string.grid_title));
             }
@@ -563,7 +549,7 @@
         }
 
         @Override
-        Fragment getFragment() {
+        public Fragment getFragment(Context c) {
             if (mFragment == null) {
                 mFragment = ClockFragment.newInstance(getString(R.string.clock_title));
             }
diff --git a/src/com/android/customization/widget/OptionSelectorController.java b/src/com/android/customization/widget/OptionSelectorController.java
index db09aa6..47b0aa7 100644
--- a/src/com/android/customization/widget/OptionSelectorController.java
+++ b/src/com/android/customization/widget/OptionSelectorController.java
@@ -153,6 +153,17 @@
     }
 
     /**
+     * Notify that a given option has changed.
+     * @param option the option that changed
+     */
+    public void optionChanged(T option) {
+        if (!mOptions.contains(option)) {
+            throw new IllegalArgumentException("Invalid option");
+        }
+        mAdapter.notifyItemChanged(mOptions.indexOf(option));
+    }
+
+    /**
      * Initializes the UI for the options passed in the constructor of this class.
      */
     public void initOptions(final CustomizationManager<T> manager) {