Minimize use of header in adjusting notification colors.

Also adds support for 'inverted' icons.

Test: atest SystemUITests
Test: manual testing of numerous notification configurations
Change-Id: I6fd9aeec9f5971495d514add2bbb30b50db7d3c1
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index a54c65e..5e50b96 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -82,7 +82,6 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Gravity;
-import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.contentcapture.ContentCaptureContext;
@@ -5862,24 +5861,16 @@
         }
 
         /**
-         * Apply any necessariy colors to the small icon
+         * Apply any necessary colors to the small icon
          */
         private void processSmallIconColor(Icon smallIcon, RemoteViews contentView,
                 StandardTemplateParams p) {
             boolean colorable = !isLegacy() || getColorUtil().isGrayscaleIcon(mContext, smallIcon);
-            int color;
-            if (isColorized(p)) {
-                color = getPrimaryTextColor(p);
-            } else {
-                color = resolveContrastColor(p);
-            }
-            if (colorable) {
-                contentView.setDrawableTint(R.id.icon, false, color,
-                        PorterDuff.Mode.SRC_ATOP);
-
-            }
+            int color = isColorized(p) ? getPrimaryTextColor(p) : resolveContrastColor(p);
+            contentView.setInt(R.id.icon, "setBackgroundColor",
+                    resolveBackgroundColor(p));
             contentView.setInt(R.id.icon, "setOriginalIconColor",
-                    colorable ? color : NotificationHeaderView.NO_COLOR);
+                    colorable ? color : COLOR_INVALID);
         }
 
         /**
@@ -5892,8 +5883,8 @@
             if (largeIcon != null && isLegacy()
                     && getColorUtil().isGrayscaleIcon(mContext, largeIcon)) {
                 // resolve color will fall back to the default when legacy
-                contentView.setDrawableTint(R.id.icon, false, resolveContrastColor(p),
-                        PorterDuff.Mode.SRC_ATOP);
+                int color = resolveContrastColor(p);
+                contentView.setInt(R.id.icon, "setOriginalIconColor", color);
             }
         }
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index e28b672..355b314 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -17,7 +17,6 @@
 package android.view;
 
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Resources;
@@ -42,7 +41,6 @@
  */
 @RemoteViews.RemoteView
 public class NotificationHeaderView extends ViewGroup {
-    public static final int NO_COLOR = Notification.COLOR_INVALID;
     private final int mChildMinWidth;
     private final int mContentEndMargin;
     private final int mGravity;
@@ -294,14 +292,6 @@
         updateTouchListener();
     }
 
-    public int getOriginalIconColor() {
-        return mIcon.getOriginalIconColor();
-    }
-
-    public int getOriginalNotificationColor() {
-        return mExpandButton.getOriginalNotificationColor();
-    }
-
     public void setShowWorkBadgeAtEnd(boolean showWorkBadgeAtEnd) {
         if (showWorkBadgeAtEnd != mShowWorkBadgeAtEnd) {
             setClipToPadding(!showWorkBadgeAtEnd);
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 84cde1b..0bf323f 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -16,12 +16,15 @@
 
 package com.android.internal.widget;
 
+import static com.android.internal.widget.ColoredIconHelper.applyGrayTint;
+
 import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
@@ -48,6 +51,7 @@
     private Consumer<Integer> mOnVisibilityChangedListener;
     private Consumer<Boolean> mOnForceHiddenChangedListener;
     private int mIconColor;
+    private int mBackgroundColor;
     private boolean mWillBeForceHidden;
 
     @UnsupportedAppUsage
@@ -230,9 +234,55 @@
         return mForceHidden;
     }
 
+    /**
+     * Provides the notification's background color to the icon.  This is only used when the icon
+     * is "inverted".  This should be called before calling {@link #setOriginalIconColor(int)}.
+     */
+    @RemotableViewMethod
+    public void setBackgroundColor(int color) {
+        mBackgroundColor = color;
+    }
+
+    /**
+     * Sets the icon color. If COLOR_INVALID is set, the icon's color filter will
+     * not be altered. If there is a background drawable, this method uses the value from
+     * {@link #setBackgroundColor(int)} which must have been already called.
+     */
     @RemotableViewMethod
     public void setOriginalIconColor(int color) {
         mIconColor = color;
+        Drawable background = getBackground();
+        Drawable icon = getDrawable();
+        boolean hasColor = color != ColoredIconHelper.COLOR_INVALID;
+        if (background == null) {
+            // This is the pre-S style -- colored icon with no background.
+            if (hasColor) {
+                icon.mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+            }
+        } else {
+            // When there is a background drawable, color it with the foreground color and
+            // colorize the icon itself with the background color, creating an inverted effect.
+            if (hasColor) {
+                background.mutate().setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+                icon.mutate().setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_ATOP);
+            } else {
+                background.mutate().setColorFilter(mBackgroundColor, PorterDuff.Mode.SRC_ATOP);
+            }
+        }
+    }
+
+    /**
+     * Set the icon's color filter: to gray if true, otherwise colored.
+     * If this icon has no original color, this has no effect.
+     */
+    public void setGrayedOut(boolean grayedOut) {
+        // If there is a background drawable, then it has the foreground color and the image
+        // drawable has the background color, creating an inverted efffect.
+        Drawable drawable = getBackground();
+        if (drawable == null) {
+            drawable = getDrawable();
+        }
+        applyGrayTint(mContext, drawable, grayedOut, mIconColor);
     }
 
     public int getOriginalIconColor() {
diff --git a/core/java/com/android/internal/widget/ColoredIconHelper.java b/core/java/com/android/internal/widget/ColoredIconHelper.java
new file mode 100644
index 0000000..97e5e86
--- /dev/null
+++ b/core/java/com/android/internal/widget/ColoredIconHelper.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.ColorInt;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+
+import com.android.internal.util.ContrastColorUtil;
+
+/** Helpers for colored icons */
+final class ColoredIconHelper {
+
+    @ColorInt
+    static final int COLOR_INVALID = Notification.COLOR_INVALID;
+
+    private ColoredIconHelper() {
+    }
+
+    /**
+     * Apply a gray tint or the original color to a drawable, accounting for the night mode in
+     * selecting the gray.
+     */
+    static void applyGrayTint(Context ctx, Drawable drawable, boolean apply, int originalColor) {
+        if (originalColor == COLOR_INVALID) {
+            return;
+        }
+        if (apply) {
+            // lets gray it out
+            Configuration config = ctx.getResources().getConfiguration();
+            boolean inNightMode = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                    == Configuration.UI_MODE_NIGHT_YES;
+            int grey = ContrastColorUtil.resolveColor(ctx, Notification.COLOR_DEFAULT, inNightMode);
+            drawable.mutate().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
+        } else {
+            // lets reset it
+            drawable.mutate().setColorFilter(originalColor, PorterDuff.Mode.SRC_ATOP);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/NotificationExpandButton.java b/core/java/com/android/internal/widget/NotificationExpandButton.java
index af6ac07..986412d 100644
--- a/core/java/com/android/internal/widget/NotificationExpandButton.java
+++ b/core/java/com/android/internal/widget/NotificationExpandButton.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.widget;
 
+import static com.android.internal.widget.ColoredIconHelper.applyGrayTint;
+
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
@@ -70,6 +72,14 @@
         return mOriginalNotificationColor;
     }
 
+    /**
+     * Set the button's color filter: to gray if true, otherwise colored.
+     * If this button has no original color, this has no effect.
+     */
+    public void setGrayedOut(boolean shouldApply) {
+        applyGrayTint(mContext, getDrawable(), shouldApply, mOriginalNotificationColor);
+    }
+
     private void extendRectToMinTouchSize(Rect rect) {
         int touchTargetSize = (int) (getResources().getDisplayMetrics().density * 48);
         rect.left = rect.centerX() - touchTargetSize / 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
index b7f4e67..5976582 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationHeaderUtil.java
@@ -17,18 +17,16 @@
 package com.android.systemui.statusbar;
 
 import android.app.Notification;
-import android.content.res.Configuration;
-import android.graphics.PorterDuff;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
-import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.ImageView;
 import android.widget.TextView;
 
-import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.widget.CachingIconView;
 import com.android.internal.widget.ConversationLayout;
+import com.android.internal.widget.NotificationExpandButton;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationContentView;
 
@@ -67,30 +65,14 @@
     private final static ResultApplicator mGreyApplicator = new ResultApplicator() {
         @Override
         public void apply(View parent, View view, boolean apply, boolean reset) {
-            NotificationHeaderView header = (NotificationHeaderView) view;
-            ImageView icon = view.findViewById(com.android.internal.R.id.icon);
-            ImageView expand = view.findViewById(com.android.internal.R.id.expand_button);
-            applyToChild(icon, apply, header.getOriginalIconColor());
-            applyToChild(expand, apply, header.getOriginalNotificationColor());
-        }
-
-        private void applyToChild(View view, boolean shouldApply, int originalColor) {
-            if (originalColor != NotificationHeaderView.NO_COLOR) {
-                ImageView imageView = (ImageView) view;
-                imageView.getDrawable().mutate();
-                if (shouldApply) {
-                    // lets gray it out
-                    Configuration config = view.getContext().getResources().getConfiguration();
-                    boolean inNightMode = (config.uiMode & Configuration.UI_MODE_NIGHT_MASK)
-                            == Configuration.UI_MODE_NIGHT_YES;
-                    int grey = ContrastColorUtil.resolveColor(view.getContext(),
-                            Notification.COLOR_DEFAULT, inNightMode);
-                    imageView.getDrawable().setColorFilter(grey, PorterDuff.Mode.SRC_ATOP);
-                } else {
-                    // lets reset it
-                    imageView.getDrawable().setColorFilter(originalColor,
-                            PorterDuff.Mode.SRC_ATOP);
-                }
+            CachingIconView icon = view.findViewById(com.android.internal.R.id.icon);
+            if (icon != null) {
+                icon.setGrayedOut(apply);
+            }
+            NotificationExpandButton expand =
+                    view.findViewById(com.android.internal.R.id.expand_button);
+            if (expand != null) {
+                expand.setGrayedOut(apply);
             }
         }
     };