Animate the color transition (active<->inactive) in the mode header icon

Also, don't apply the layout params, etc on each call to updateState - once per displayPreference is enough.

Fixes: 356399449
Bug: 357861830
Test: manual
Flag: android.app.modes_ui
Change-Id: I6967ea1745377d0f514ca0f68101043f017a8fd7
diff --git a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java
index 06a30fa..81b53cc 100644
--- a/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/AbstractZenModeHeaderController.java
@@ -15,6 +15,8 @@
  */
 package com.android.settings.notification.modes;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 import android.app.Flags;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
@@ -22,8 +24,8 @@
 import android.widget.ImageView;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -32,13 +34,15 @@
 import com.android.settingslib.notification.modes.ZenMode;
 import com.android.settingslib.widget.LayoutPreference;
 
-import java.util.function.Consumer;
+import com.google.common.base.Objects;
+
 import java.util.function.Function;
 
 abstract class AbstractZenModeHeaderController extends AbstractZenModePreferenceController {
 
     private final DashboardFragment mFragment;
     private EntityHeaderController mHeaderController;
+    private String mCurrentIconKey;
 
     AbstractZenModeHeaderController(
             @NonNull Context context,
@@ -53,40 +57,44 @@
         return Flags.modesApi() && Flags.modesUi();
     }
 
-    protected void updateIcon(Preference preference, @NonNull ZenMode zenMode, int iconSizePx,
-            Function<Drawable, Drawable> modeIconStylist,
-            @Nullable Consumer<ImageView> iconViewCustomizer) {
-        if (mFragment == null) {
-            return;
-        }
+    protected void setUpHeader(PreferenceScreen screen, int iconSizePx) {
+        LayoutPreference preference = checkNotNull(screen.findPreference(getPreferenceKey()));
         preference.setSelectable(false);
 
         if (mHeaderController == null) {
-            final LayoutPreference pref = (LayoutPreference) preference;
             mHeaderController = EntityHeaderController.newInstance(
                     mFragment.getActivity(),
                     mFragment,
-                    pref.findViewById(R.id.entity_header));
+                    preference.findViewById(R.id.entity_header));
         }
 
-        ImageView iconView = ((LayoutPreference) preference).findViewById(R.id.entity_header_icon);
-        if (iconView != null) {
-            if (iconViewCustomizer != null) {
-                iconViewCustomizer.accept(iconView);
-            }
-            ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
-            if (layoutParams.width != iconSizePx || layoutParams.height != iconSizePx) {
-                layoutParams.width = iconSizePx;
-                layoutParams.height = iconSizePx;
-                iconView.setLayoutParams(layoutParams);
-            }
+        ImageView iconView = checkNotNull(preference.findViewById(R.id.entity_header_icon));
+        ViewGroup.LayoutParams layoutParams = iconView.getLayoutParams();
+        if (layoutParams.width != iconSizePx || layoutParams.height != iconSizePx) {
+            layoutParams.width = iconSizePx;
+            layoutParams.height = iconSizePx;
+            iconView.setLayoutParams(layoutParams);
         }
+    }
 
-        FutureUtil.whenDone(
-                zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
-                icon -> mHeaderController
-                        .setIcon(modeIconStylist.apply(icon))
-                        .done(/* rebindActions= */ false),
-                mContext.getMainExecutor());
+    protected void updateIcon(Preference preference, @NonNull ZenMode zenMode,
+            Function<Drawable, Drawable> iconStylist, boolean isSelected) {
+
+        ImageView iconView = checkNotNull(
+                ((LayoutPreference) preference).findViewById(R.id.entity_header_icon));
+        iconView.setSelected(isSelected);
+
+        if (!Objects.equal(mCurrentIconKey, zenMode.getIconKey())) {
+            mCurrentIconKey = zenMode.getIconKey();
+            FutureUtil.whenDone(
+                    zenMode.getIcon(mContext, ZenIconLoader.getInstance()),
+                    icon -> {
+                        checkNotNull(mHeaderController)
+                                .setIcon(iconStylist.apply(icon))
+                                .done(/* rebindActions= */ false);
+                        iconView.jumpDrawablesToCurrentState(); // Skip animation on first load.
+                    },
+                    mContext.getMainExecutor());
+        }
     }
 }
diff --git a/src/com/android/settings/notification/modes/IconUtil.java b/src/com/android/settings/notification/modes/IconUtil.java
index 43161ce..dc4d875 100644
--- a/src/com/android/settings/notification/modes/IconUtil.java
+++ b/src/com/android/settings/notification/modes/IconUtil.java
@@ -30,7 +30,9 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.StateListDrawable;
 import android.graphics.drawable.shapes.OvalShape;
+import android.util.StateSet;
 import android.view.Gravity;
 
 import androidx.annotation.AttrRes;
@@ -65,20 +67,42 @@
 
     /**
      * Returns a variant of the supplied mode icon to be used as the header in the mode page. The
-     * inner icon is 64x64 dp and it's contained in a 12-sided-cookie of 136dp diameter. It's
-     * tinted with the "material secondary" color combination and the "selected" color variant
-     * should be used for modes currently active.
+     * mode icon is contained in a 12-sided-cookie. The color combination is "material secondary"
+     * when unselected and "material primary" when selected; the switch between these two color sets
+     * is animated with a cross-fade. The selected colors should be used when the mode is currently
+     * active.
      */
     static Drawable makeModeHeader(@NonNull Context context, Drawable modeIcon) {
-        return composeIcons(
-                checkNotNull(context.getDrawable(R.drawable.ic_zen_mode_icon_cookie)),
-                context.getColorStateList(R.color.modes_icon_selectable_background),
-                context.getResources().getDimensionPixelSize(
-                        R.dimen.zen_mode_header_size),
+        Resources res = context.getResources();
+        Drawable background = checkNotNull(context.getDrawable(R.drawable.ic_zen_mode_icon_cookie));
+        @Px int outerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_size);
+        @Px int innerSizePx = res.getDimensionPixelSize(R.dimen.zen_mode_header_inner_icon_size);
+
+        Drawable base = composeIcons(
+                background,
+                Utils.getColorAttr(context,
+                        com.android.internal.R.attr.materialColorSecondaryContainer),
+                outerSizePx,
                 modeIcon,
-                context.getColorStateList(R.color.modes_icon_selectable_icon),
-                context.getResources().getDimensionPixelSize(
-                        R.dimen.zen_mode_header_inner_icon_size));
+                Utils.getColorAttr(context,
+                        com.android.internal.R.attr.materialColorOnSecondaryContainer),
+                innerSizePx);
+
+        Drawable selected = composeIcons(
+                background,
+                Utils.getColorAttr(context, com.android.internal.R.attr.materialColorPrimary),
+                outerSizePx,
+                modeIcon,
+                Utils.getColorAttr(context, com.android.internal.R.attr.materialColorOnPrimary),
+                innerSizePx);
+
+        StateListDrawable result = new StateListDrawable();
+        result.setEnterFadeDuration(res.getInteger(android.R.integer.config_mediumAnimTime));
+        result.setExitFadeDuration(res.getInteger(android.R.integer.config_mediumAnimTime));
+        result.addState(new int[] { android.R.attr.state_selected }, selected);
+        result.addState(StateSet.WILD_CARD, base);
+        result.setBounds(0, 0, outerSizePx, outerSizePx);
+        return result;
     }
 
     /**
diff --git a/src/com/android/settings/notification/modes/ZenModeHeaderController.java b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
index c4f3dd1..ae6eacc 100644
--- a/src/com/android/settings/notification/modes/ZenModeHeaderController.java
+++ b/src/com/android/settings/notification/modes/ZenModeHeaderController.java
@@ -19,6 +19,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -34,10 +35,16 @@
     }
 
     @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        setUpHeader(screen,
+                mContext.getResources().getDimensionPixelSize(R.dimen.zen_mode_header_size));
+    }
+
+    @Override
     public void updateState(Preference preference, @NonNull ZenMode zenMode) {
         updateIcon(preference, zenMode,
-                mContext.getResources().getDimensionPixelSize(R.dimen.zen_mode_header_size),
                 icon -> IconUtil.makeModeHeader(mContext, icon),
-                iconView -> iconView.setSelected(zenMode.isActive()));
+                /* isSelected= */ zenMode.isActive());
     }
 }
diff --git a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
index a7adf6c..6c8d41f 100644
--- a/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
+++ b/src/com/android/settings/notification/modes/ZenModeIconPickerIconPreferenceController.java
@@ -20,6 +20,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
 import com.android.settings.dashboard.DashboardFragment;
@@ -34,11 +35,16 @@
     }
 
     @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        setUpHeader(screen, mContext.getResources().getDimensionPixelSize(
+                R.dimen.zen_mode_icon_list_header_circle_diameter));
+    }
+
+    @Override
     void updateState(Preference preference, @NonNull ZenMode zenMode) {
         updateIcon(preference, zenMode,
-                mContext.getResources().getDimensionPixelSize(
-                        R.dimen.zen_mode_icon_list_header_circle_diameter),
                 icon -> IconUtil.makeIconPickerHeader(mContext, icon),
-                null);
+                /* isSelected= */ false);
     }
 }