Handle night mode for notifications.

When the system detects a night mode change, it will reload the
resources and relayout the notifications.

Also, allow the text in the Notification to take night mode into
account. Add configuration to allow Android Auto embedded to not tint
certain elements of the UI.

Test: booted on phone and Android Auto headunit
Bug: 33210494
Change-Id: I261813e5795b047bdfc4f77b88e1b01cc72e3216
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4f9ed83..1d1a590 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -33,6 +33,8 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ShortcutInfo;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -2752,6 +2754,9 @@
         private ArrayList<Action> mOriginalActions;
         private boolean mRebuildStyledRemoteViews;
 
+        private boolean mTintActionButtons;
+        private boolean mInNightMode;
+
         /**
          * Constructs a new Builder with the defaults:
          *
@@ -2783,6 +2788,14 @@
          */
         public Builder(Context context, Notification toAdopt) {
             mContext = context;
+            Resources res = mContext.getResources();
+            mTintActionButtons = res.getBoolean(R.bool.config_tintNotificationActionButtons);
+
+            if (res.getBoolean(R.bool.config_enableNightMode)) {
+                Configuration currentConfig = res.getConfiguration();
+                mInNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+                        == Configuration.UI_MODE_NIGHT_YES;
+            }
 
             if (toAdopt == null) {
                 mN = new Notification();
@@ -4631,14 +4644,14 @@
                     // We need to set the text color as well since changing a text to uppercase
                     // clears its spans.
                     button.setTextColor(R.id.action0, outResultColor[0]);
-                } else if (mN.color != COLOR_DEFAULT && !isColorized()) {
+                } else if (mN.color != COLOR_DEFAULT && !isColorized() && mTintActionButtons) {
                     button.setTextColor(R.id.action0,resolveContrastColor());
                 }
             } else {
                 button.setTextViewText(R.id.action0, processLegacyText(action.title));
                 if (isColorized() && !ambient) {
                     setTextViewColorPrimary(button, R.id.action0);
-                } else if (mN.color != COLOR_DEFAULT) {
+                } else if (mN.color != COLOR_DEFAULT && mTintActionButtons) {
                     button.setTextColor(R.id.action0,
                             ambient ? resolveAmbientColor() : resolveContrastColor());
                 }
@@ -4717,7 +4730,7 @@
                             int[] newColors = new int[colors.length];
                             for (int i = 0; i < newColors.length; i++) {
                                 newColors[i] = NotificationColorUtil.ensureLargeTextContrast(
-                                        colors[i], background);
+                                        colors[i], background, mInNightMode);
                             }
                             textColor = new ColorStateList(textColor.getStates().clone(),
                                     newColors);
@@ -4736,7 +4749,7 @@
                         ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
                         int foregroundColor = originalSpan.getForegroundColor();
                         foregroundColor = NotificationColorUtil.ensureLargeTextContrast(
-                                foregroundColor, background);
+                                foregroundColor, background, mInNightMode);
                         resultSpan = new ForegroundColorSpan(foregroundColor);
                         if (fullLength) {
                             outResultColor[0] = ColorStateList.valueOf(foregroundColor);
@@ -4831,7 +4844,7 @@
                 color = mSecondaryTextColor;
             } else {
                 color = NotificationColorUtil.resolveContrastColor(mContext, mN.color,
-                        background);
+                        background, mInNightMode);
             }
             if (Color.alpha(color) < 255) {
                 // alpha doesn't go well for color filters, so let's blend it manually
@@ -5064,6 +5077,10 @@
             return mN.isColorized();
         }
 
+        private boolean shouldTintActionButtons() {
+            return mTintActionButtons;
+        }
+
         private boolean textColorsNeedInversion() {
             if (mStyle == null || !MediaStyle.class.equals(mStyle.getClass())) {
                 return false;
@@ -6067,6 +6084,8 @@
             BidiFormatter bidi = BidiFormatter.getInstance();
             SpannableStringBuilder sb = new SpannableStringBuilder();
             boolean colorize = builder.isColorized();
+            TextAppearanceSpan colorSpan;
+            CharSequence messageName;
             if (TextUtils.isEmpty(m.mSender)) {
                 CharSequence replyName = mUserDisplayName == null ? "" : mUserDisplayName;
                 sb.append(bidi.unicodeWrap(replyName),
@@ -6595,8 +6614,16 @@
             RemoteViews button = new BuilderRemoteViews(mBuilder.mContext.getApplicationInfo(),
                     R.layout.notification_material_media_action);
             button.setImageViewIcon(R.id.action0, action.getIcon());
-            button.setDrawableParameters(R.id.action0, false, -1, color, PorterDuff.Mode.SRC_ATOP,
-                    -1);
+
+            // If the action buttons should not be tinted, then just use the default
+            // notification color. Otherwise, just use the passed-in color.
+            int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized()
+                    ? color
+                    : NotificationColorUtil.resolveColor(mBuilder.mContext,
+                            Notification.COLOR_DEFAULT);
+
+            button.setDrawableParameters(R.id.action0, false, -1, tintColor,
+                    PorterDuff.Mode.SRC_ATOP, -1);
             if (!tombstone) {
                 button.setOnClickPendingIntent(R.id.action0, action.actionIntent);
             }
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java
index 1ba92bf..5c9f1c6 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/NotificationColorUtil.java
@@ -360,20 +360,28 @@
         return findContrastColorAgainstDark(color, Color.BLACK, true /* fg */, 12);
     }
 
-    /**
-     * Finds a text color with sufficient contrast over bg that has the same hue as the original
-     * color, assuming it is for large text.
+     /**
+     * Finds a large text color with sufficient contrast over bg that has the same or darker hue as
+     * the original color, depending on the value of {@code isBgDarker}.
+     *
+     * @param isBgDarker {@code true} if {@code bg} is darker than {@code color}.
      */
-    public static int ensureLargeTextContrast(int color, int bg) {
-        return findContrastColor(color, bg, true, 3);
+    public static int ensureLargeTextContrast(int color, int bg, boolean isBgDarker) {
+        return isBgDarker
+                ? findContrastColorAgainstDark(color, bg, true, 3)
+                : findContrastColor(color, bg, true, 3);
     }
 
     /**
-     * Finds a text color with sufficient contrast over bg that has the same hue as the original
-     * color.
+     * Finds a text color with sufficient contrast over bg that has the same or darker hue as the
+     * original color, depending on the value of {@code isBgDarker}.
+     *
+     * @param isBgDarker {@code true} if {@code bg} is darker than {@code color}.
      */
-    private static int ensureTextContrast(int color, int bg) {
-        return findContrastColor(color, bg, true, 4.5);
+    private static int ensureTextContrast(int color, int bg, boolean isBgDarker) {
+        return isBgDarker
+                ? findContrastColorAgainstDark(color, bg, true, 4.5)
+                : findContrastColor(color, bg, true, 4.5);
     }
 
     /** Finds a background color for a text view with given text color and hint text color, that
@@ -402,22 +410,37 @@
 
     /**
      * Resolves a Notification's color such that it has enough contrast to be used as the
+     * color for the Notification's action and header text on a background that is lighter than
+     * {@code notificationColor}.
+     *
+     * @see {@link #resolveContrastColor(Context, int, boolean)}
+     */
+    public static int resolveContrastColor(Context context, int notificationColor,
+            int backgroundColor) {
+        return NotificationColorUtil.resolveContrastColor(context, notificationColor,
+                backgroundColor, false /* isDark */);
+    }
+
+    /**
+     * Resolves a Notification's color such that it has enough contrast to be used as the
      * color for the Notification's action and header text.
      *
      * @param notificationColor the color of the notification or {@link Notification#COLOR_DEFAULT}
      * @param backgroundColor the background color to ensure the contrast against.
+     * @param isDark whether or not the {@code notificationColor} will be placed on a background
+     *               that is darker than the color itself
      * @return a color of the same hue with enough contrast against the backgrounds.
      */
     public static int resolveContrastColor(Context context, int notificationColor,
-            int backgroundColor) {
+            int backgroundColor, boolean isDark) {
         final int resolvedColor = resolveColor(context, notificationColor);
 
         final int actionBg = context.getColor(
                 com.android.internal.R.color.notification_action_list);
 
         int color = resolvedColor;
-        color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg);
-        color = NotificationColorUtil.ensureTextContrast(color, backgroundColor);
+        color = NotificationColorUtil.ensureLargeTextContrast(color, actionBg, isDark);
+        color = NotificationColorUtil.ensureTextContrast(color, backgroundColor, isDark);
 
         if (color != resolvedColor) {
             if (DEBUG){
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 76cee70..4a000b9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2978,4 +2978,11 @@
 
     <!-- An array of packages that need to be treated as type service in battery settings -->
     <string-array translatable="false" name="config_batteryPackageTypeService"/>
+
+    <!-- Flag indicating whether or not to enable night mode detection. -->
+    <bool name="config_enableNightMode">false</bool>
+
+    <!-- Flag indicating that the actions buttons for a notification should be tinted with by the
+         color supplied by the Notification.Builder if present. -->
+    <bool name="config_tintNotificationActionButtons">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 241886c..023b032 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1749,6 +1749,8 @@
   <java-symbol type="bool" name="config_automatic_brightness_available" />
   <java-symbol type="bool" name="config_autoBrightnessResetAmbientLuxAfterWarmUp" />
   <java-symbol type="bool" name="config_notificationHeaderClickableForExpand" />
+  <java-symbol type="bool" name="config_enableNightMode" />
+  <java-symbol type="bool" name="config_tintNotificationActionButtons" />
   <java-symbol type="bool" name="config_dozeAfterScreenOff" />
   <java-symbol type="bool" name="config_enableActivityRecognitionHardwareOverlay" />
   <java-symbol type="bool" name="config_enableFusedLocationOverlay" />