More conservative dark text calculation
Refactored WallpaperColors to use constrast ratio instead of luminance
for detecting dark pixels. Also using a contrast more conservative than
what GAR requires while decreasing the dark area threshold.
Change-Id: I67b799be4b7ccd50bb3e63c6179d513b9b76446b
Fixes: 76435920
Test: manually set various wallpapers
Test: use new debug flag to verify which pixel is actually dark
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8d56c3e..01e6f13 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -16,7 +16,7 @@
package android.app;
-import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
+import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
@@ -79,7 +79,7 @@
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.NotificationColorUtil;
+import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -3188,7 +3188,7 @@
private Style mStyle;
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
private ArrayList<Person> mPersonList = new ArrayList<>();
- private NotificationColorUtil mColorUtil;
+ private ContrastColorUtil mColorUtil;
private boolean mIsLegacy;
private boolean mIsLegacyInitialized;
@@ -3319,9 +3319,9 @@
}
}
- private NotificationColorUtil getColorUtil() {
+ private ContrastColorUtil getColorUtil() {
if (mColorUtil == null) {
- mColorUtil = NotificationColorUtil.getInstance(mContext);
+ mColorUtil = ContrastColorUtil.getInstance(mContext);
}
return mColorUtil;
}
@@ -4440,7 +4440,7 @@
private CharSequence processTextSpans(CharSequence text) {
if (hasForegroundColor()) {
- return NotificationColorUtil.clearColorSpans(text);
+ return ContrastColorUtil.clearColorSpans(text);
}
return text;
}
@@ -4486,20 +4486,20 @@
|| mTextColorsAreForBackground != backgroundColor) {
mTextColorsAreForBackground = backgroundColor;
if (!hasForegroundColor() || !isColorized()) {
- mPrimaryTextColor = NotificationColorUtil.resolvePrimaryColor(mContext,
+ mPrimaryTextColor = ContrastColorUtil.resolvePrimaryColor(mContext,
backgroundColor);
- mSecondaryTextColor = NotificationColorUtil.resolveSecondaryColor(mContext,
+ mSecondaryTextColor = ContrastColorUtil.resolveSecondaryColor(mContext,
backgroundColor);
if (backgroundColor != COLOR_DEFAULT && isColorized()) {
- mPrimaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
+ mPrimaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
mPrimaryTextColor, backgroundColor, 4.5);
- mSecondaryTextColor = NotificationColorUtil.findAlphaToMeetContrast(
+ mSecondaryTextColor = ContrastColorUtil.findAlphaToMeetContrast(
mSecondaryTextColor, backgroundColor, 4.5);
}
} else {
- double backLum = NotificationColorUtil.calculateLuminance(backgroundColor);
- double textLum = NotificationColorUtil.calculateLuminance(mForegroundColor);
- double contrast = NotificationColorUtil.calculateContrast(mForegroundColor,
+ double backLum = ContrastColorUtil.calculateLuminance(backgroundColor);
+ double textLum = ContrastColorUtil.calculateLuminance(mForegroundColor);
+ double contrast = ContrastColorUtil.calculateContrast(mForegroundColor,
backgroundColor);
// We only respect the given colors if worst case Black or White still has
// contrast
@@ -4509,46 +4509,46 @@
&& !satisfiesTextContrast(backgroundColor, Color.WHITE);
if (contrast < 4.5f) {
if (backgroundLight) {
- mSecondaryTextColor = NotificationColorUtil.findContrastColor(
+ mSecondaryTextColor = ContrastColorUtil.findContrastColor(
mForegroundColor,
backgroundColor,
true /* findFG */,
4.5f);
- mPrimaryTextColor = NotificationColorUtil.changeColorLightness(
+ mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_LIGHT);
} else {
mSecondaryTextColor =
- NotificationColorUtil.findContrastColorAgainstDark(
+ ContrastColorUtil.findContrastColorAgainstDark(
mForegroundColor,
backgroundColor,
true /* findFG */,
4.5f);
- mPrimaryTextColor = NotificationColorUtil.changeColorLightness(
+ mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
mSecondaryTextColor, -LIGHTNESS_TEXT_DIFFERENCE_DARK);
}
} else {
mPrimaryTextColor = mForegroundColor;
- mSecondaryTextColor = NotificationColorUtil.changeColorLightness(
+ mSecondaryTextColor = ContrastColorUtil.changeColorLightness(
mPrimaryTextColor, backgroundLight ? LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: LIGHTNESS_TEXT_DIFFERENCE_DARK);
- if (NotificationColorUtil.calculateContrast(mSecondaryTextColor,
+ if (ContrastColorUtil.calculateContrast(mSecondaryTextColor,
backgroundColor) < 4.5f) {
// oh well the secondary is not good enough
if (backgroundLight) {
- mSecondaryTextColor = NotificationColorUtil.findContrastColor(
+ mSecondaryTextColor = ContrastColorUtil.findContrastColor(
mSecondaryTextColor,
backgroundColor,
true /* findFG */,
4.5f);
} else {
mSecondaryTextColor
- = NotificationColorUtil.findContrastColorAgainstDark(
+ = ContrastColorUtil.findContrastColorAgainstDark(
mSecondaryTextColor,
backgroundColor,
true /* findFG */,
4.5f);
}
- mPrimaryTextColor = NotificationColorUtil.changeColorLightness(
+ mPrimaryTextColor = ContrastColorUtil.changeColorLightness(
mSecondaryTextColor, backgroundLight
? -LIGHTNESS_TEXT_DIFFERENCE_LIGHT
: -LIGHTNESS_TEXT_DIFFERENCE_DARK);
@@ -5260,7 +5260,7 @@
ColorStateList[] outResultColor = null;
int background = resolveBackgroundColor();
if (isLegacy()) {
- title = NotificationColorUtil.clearColorSpans(title);
+ title = ContrastColorUtil.clearColorSpans(title);
} else {
outResultColor = new ColorStateList[1];
title = ensureColorSpanContrast(title, background, outResultColor);
@@ -5273,7 +5273,7 @@
// There's a span spanning the full text, let's take it and use it as the
// background color
background = outResultColor[0].getDefaultColor();
- int textColor = NotificationColorUtil.resolvePrimaryColor(mContext,
+ int textColor = ContrastColorUtil.resolvePrimaryColor(mContext,
background);
button.setTextColor(R.id.action0, textColor);
rippleColor = textColor;
@@ -5334,7 +5334,7 @@
int[] colors = textColor.getColors();
int[] newColors = new int[colors.length];
for (int i = 0; i < newColors.length; i++) {
- newColors[i] = NotificationColorUtil.ensureLargeTextContrast(
+ newColors[i] = ContrastColorUtil.ensureLargeTextContrast(
colors[i], background, mInNightMode);
}
textColor = new ColorStateList(textColor.getStates().clone(),
@@ -5354,7 +5354,7 @@
} else if (resultSpan instanceof ForegroundColorSpan) {
ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan;
int foregroundColor = originalSpan.getForegroundColor();
- foregroundColor = NotificationColorUtil.ensureLargeTextContrast(
+ foregroundColor = ContrastColorUtil.ensureLargeTextContrast(
foregroundColor, background, mInNightMode);
if (fullLength) {
outResultColor[0] = ColorStateList.valueOf(foregroundColor);
@@ -5454,14 +5454,14 @@
com.android.internal.R.color.notification_material_background_color);
if (mN.color == COLOR_DEFAULT) {
ensureColors();
- color = NotificationColorUtil.resolveDefaultColor(mContext, background);
+ color = ContrastColorUtil.resolveDefaultColor(mContext, background);
} else {
- color = NotificationColorUtil.resolveContrastColor(mContext, mN.color,
+ color = ContrastColorUtil.resolveContrastColor(mContext, mN.color,
background, mInNightMode);
}
if (Color.alpha(color) < 255) {
// alpha doesn't go well for color filters, so let's blend it manually
- color = NotificationColorUtil.compositeColors(color, background);
+ color = ContrastColorUtil.compositeColors(color, background);
}
mCachedContrastColorIsFor = mN.color;
return mCachedContrastColor = color;
@@ -5473,10 +5473,10 @@
}
int background = mContext.getColor(
com.android.internal.R.color.notification_material_background_color);
- mNeutralColor = NotificationColorUtil.resolveDefaultColor(mContext, background);
+ mNeutralColor = ContrastColorUtil.resolveDefaultColor(mContext, background);
if (Color.alpha(mNeutralColor) < 255) {
// alpha doesn't go well for color filters, so let's blend it manually
- mNeutralColor = NotificationColorUtil.compositeColors(mNeutralColor, background);
+ mNeutralColor = ContrastColorUtil.compositeColors(mNeutralColor, background);
}
return mNeutralColor;
}
@@ -5485,7 +5485,7 @@
if (mCachedAmbientColorIsFor == mN.color && mCachedAmbientColorIsFor != COLOR_INVALID) {
return mCachedAmbientColor;
}
- final int contrasted = NotificationColorUtil.resolveAmbientColor(mContext, mN.color);
+ final int contrasted = ContrastColorUtil.resolveAmbientColor(mContext, mN.color);
mCachedAmbientColorIsFor = mN.color;
return mCachedAmbientColor = contrasted;
@@ -7846,7 +7846,7 @@
// notification color. Otherwise, just use the passed-in color.
int tintColor = mBuilder.shouldTintActionButtons() || mBuilder.isColorized()
? color
- : NotificationColorUtil.resolveColor(mBuilder.mContext,
+ : ContrastColorUtil.resolveColor(mBuilder.mContext,
Notification.COLOR_DEFAULT);
button.setDrawableTint(R.id.action0, false, tintColor,
diff --git a/core/java/android/app/WallpaperColors.java b/core/java/android/app/WallpaperColors.java
index 60e8a12..626b3be 100644
--- a/core/java/android/app/WallpaperColors.java
+++ b/core/java/android/app/WallpaperColors.java
@@ -25,12 +25,15 @@
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.Log;
import android.util.Size;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.graphics.palette.Palette;
import com.android.internal.graphics.palette.VariationalKMeansQuantizer;
+import com.android.internal.util.ContrastColorUtil;
+import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -44,6 +47,8 @@
*/
public final class WallpaperColors implements Parcelable {
+ private static final boolean DEBUG_DARK_PIXELS = false;
+
/**
* Specifies that dark text is preferred over the current wallpaper for best presentation.
* <p>
@@ -83,8 +88,8 @@
private static final float BRIGHT_IMAGE_MEAN_LUMINANCE = 0.75f;
// We also check if the image has dark pixels in it,
// to avoid bright images with some dark spots.
- private static final float DARK_PIXEL_LUMINANCE = 0.45f;
- private static final float MAX_DARK_AREA = 0.05f;
+ private static final float DARK_PIXEL_CONTRAST = 6f;
+ private static final float MAX_DARK_AREA = 0.025f;
private final ArrayList<Color> mMainColors;
private int mColorHints;
@@ -382,8 +387,13 @@
final int alpha = Color.alpha(pixels[i]);
// Make sure we don't have a dark pixel mass that will
// make text illegible.
- if (luminance < DARK_PIXEL_LUMINANCE && alpha != 0) {
+ final boolean satisfiesTextContrast = ContrastColorUtil
+ .calculateContrast(pixels[i], Color.BLACK) > DARK_PIXEL_CONTRAST;
+ if (!satisfiesTextContrast && alpha != 0) {
darkPixels++;
+ if (DEBUG_DARK_PIXELS) {
+ pixels[i] = Color.RED;
+ }
}
totalLuminance += luminance;
}
@@ -397,6 +407,18 @@
hints |= HINT_SUPPORTS_DARK_THEME;
}
+ if (DEBUG_DARK_PIXELS) {
+ try (FileOutputStream out = new FileOutputStream("/data/pixels.png")) {
+ source.setPixels(pixels, 0, source.getWidth(), 0, 0, source.getWidth(),
+ source.getHeight());
+ source.compress(Bitmap.CompressFormat.PNG, 100, out);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ Log.d("WallpaperColors", "l: " + meanLuminance + ", d: " + darkPixels +
+ " maxD: " + maxDarkPixels + " numPixels: " + pixels.length);
+ }
+
return hints;
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 4865dab..22c840b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -68,7 +68,7 @@
import android.widget.AdapterView.OnItemClickListener;
import com.android.internal.R;
-import com.android.internal.util.NotificationColorUtil;
+import com.android.internal.util.ContrastColorUtil;
import com.android.internal.util.Preconditions;
import java.lang.annotation.ElementType;
@@ -2155,7 +2155,7 @@
View v = viewsToProcess.pop();
if (v instanceof TextView) {
TextView textView = (TextView) v;
- textView.setText(NotificationColorUtil.clearColorSpans(textView.getText()));
+ textView.setText(ContrastColorUtil.clearColorSpans(textView.getText()));
textView.setTextColor(textColor);
}
if (v instanceof ViewGroup) {
diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
similarity index 97%
rename from core/java/com/android/internal/util/NotificationColorUtil.java
rename to core/java/com/android/internal/util/ContrastColorUtil.java
index 318bccf..60dd86b 100644
--- a/core/java/com/android/internal/util/NotificationColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -48,13 +48,13 @@
*
* @hide
*/
-public class NotificationColorUtil {
+public class ContrastColorUtil {
- private static final String TAG = "NotificationColorUtil";
+ private static final String TAG = "ContrastColorUtil";
private static final boolean DEBUG = false;
private static final Object sLock = new Object();
- private static NotificationColorUtil sInstance;
+ private static ContrastColorUtil sInstance;
private final ImageUtils mImageUtils = new ImageUtils();
private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache =
@@ -62,16 +62,16 @@
private final int mGrayscaleIconMaxSize; // @dimen/notification_large_icon_width (64dp)
- public static NotificationColorUtil getInstance(Context context) {
+ public static ContrastColorUtil getInstance(Context context) {
synchronized (sLock) {
if (sInstance == null) {
- sInstance = new NotificationColorUtil(context);
+ sInstance = new ContrastColorUtil(context);
}
return sInstance;
}
}
- private NotificationColorUtil(Context context) {
+ private ContrastColorUtil(Context context) {
mGrayscaleIconMaxSize = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_large_icon_width);
}
@@ -470,7 +470,7 @@
*/
public static int resolveContrastColor(Context context, int notificationColor,
int backgroundColor) {
- return NotificationColorUtil.resolveContrastColor(context, notificationColor,
+ return ContrastColorUtil.resolveContrastColor(context, notificationColor,
backgroundColor, false /* isDark */);
}
@@ -489,7 +489,7 @@
final int resolvedColor = resolveColor(context, notificationColor);
int color = resolvedColor;
- color = NotificationColorUtil.ensureTextContrast(color, backgroundColor, isDark);
+ color = ContrastColorUtil.ensureTextContrast(color, backgroundColor, isDark);
if (color != resolvedColor) {
if (DEBUG){
@@ -497,7 +497,7 @@
"Enhanced contrast of notification for %s"
+ " and %s (over background) by changing #%s to %s",
context.getPackageName(),
- NotificationColorUtil.contrastChange(resolvedColor, color, backgroundColor),
+ ContrastColorUtil.contrastChange(resolvedColor, color, backgroundColor),
Integer.toHexString(resolvedColor), Integer.toHexString(color)));
}
}
@@ -523,7 +523,7 @@
final int resolvedColor = resolveColor(context, notificationColor);
int color = resolvedColor;
- color = NotificationColorUtil.ensureTextContrastOnBlack(color);
+ color = ContrastColorUtil.ensureTextContrastOnBlack(color);
if (color != resolvedColor) {
if (DEBUG){
@@ -531,7 +531,7 @@
"Ambient contrast of notification for %s is %s (over black)"
+ " by changing #%s to #%s",
context.getPackageName(),
- NotificationColorUtil.contrastChange(resolvedColor, color, Color.BLACK),
+ ContrastColorUtil.contrastChange(resolvedColor, color, Color.BLACK),
Integer.toHexString(resolvedColor), Integer.toHexString(color)));
}
}
@@ -609,7 +609,7 @@
}
public static boolean satisfiesTextContrast(int backgroundColor, int foregroundColor) {
- return NotificationColorUtil.calculateContrast(foregroundColor, backgroundColor) >= 4.5;
+ return ContrastColorUtil.calculateContrast(foregroundColor, backgroundColor) >= 4.5;
}
/**
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 0fd6109..514874b 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -45,7 +45,7 @@
import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.util.NotificationColorUtil;
+import com.android.internal.util.ContrastColorUtil;
import java.util.ArrayList;
import java.util.List;
@@ -315,13 +315,13 @@
}
private int findColor(CharSequence senderName, int layoutColor) {
- double luminance = NotificationColorUtil.calculateLuminance(layoutColor);
+ double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
// we need to offset the range if the luminance is too close to the borders
shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
- return NotificationColorUtil.getShiftedColor(layoutColor,
+ return ContrastColorUtil.getShiftedColor(layoutColor,
(int) (shift * COLOR_SHIFT_AMOUNT));
}
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 9ab7544..e1cb911 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -16,7 +16,7 @@
package android.app;
-import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast;
+import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;