Loading core/java/android/app/Notification.java +238 −216 File changed.Preview size limit exceeded, changes collapsed. Show changes core/java/com/android/internal/util/ContrastColorUtil.java +4 −1 Original line number Diff line number Diff line Loading @@ -595,7 +595,9 @@ public class ContrastColorUtil { if (backgroundColor == Notification.COLOR_DEFAULT) { return !defaultBackgroundIsDark; } return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5; // Color contrast ratio luminance midpoint, X: 1.05 / (X + 0.05) = (X + 0.05) / 0.05 // Solved as X = sqrt(.05 * 1.05) - 0.05 = 0.17912878474 return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.17912878474; } public static double calculateLuminance(int backgroundColor) { Loading @@ -619,6 +621,7 @@ public class ContrastColorUtil { } public static boolean isColorLight(int backgroundColor) { // TODO(b/188947832): Use 0.17912878474 instead of 0.5 to ensure better contrast return calculateLuminance(backgroundColor) > 0.5f; } Loading core/res/res/values-night/colors.xml +0 −8 Original line number Diff line number Diff line Loading @@ -17,14 +17,6 @@ NOTE: You might also want to edit: packages/SystemUI/res/values-night/colors.xml --> <resources> <!-- The primary text color if the text is on top of a dark background. This is also affects colorized notifications with dark backgrounds. --> <color name="notification_primary_text_color_dark">#ddffffff</color> <!-- The secondary text color if the text is on top of a dark background. --> <color name="notification_secondary_text_color_dark">#b2ffffff</color> <color name="notification_default_color_dark">#ddffffff</color> <color name="notification_primary_text_color_current">@color/notification_primary_text_color_dark</color> <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_dark</color> Loading core/tests/coretests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ android_test { "frameworks-core-util-lib", "mockwebserver", "guava", "androidx.core_core", "androidx.test.espresso.core", "androidx.test.ext.junit", "androidx.test.runner", Loading core/tests/coretests/src/android/app/NotificationTest.java +145 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,14 @@ package android.app; import static androidx.core.graphics.ColorUtils.calculateContrast; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; Loading @@ -27,10 +35,10 @@ import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.LocusId; import android.content.res.Configuration; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; Loading Loading @@ -325,13 +333,142 @@ public class NotificationTest { assertNull(clone.getLocusId()); } private Notification.Builder getMediaNotification() { MediaSession session = new MediaSession(mContext, "test"); return new Notification.Builder(mContext, "color") .setSmallIcon(com.android.internal.R.drawable.emergency_icon) .setContentTitle("Title") .setContentText("Text") .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken())); @Test public void testColors_ensureColors_dayMode_producesValidPalette() { Notification.Colors c = new Notification.Colors(); boolean colorized = false; boolean nightMode = false; resolveColorsInNightMode(nightMode, c, Color.BLUE, colorized); assertValid(c); } @Test public void testColors_ensureColors_nightMode_producesValidPalette() { Notification.Colors c = new Notification.Colors(); boolean colorized = false; boolean nightMode = true; resolveColorsInNightMode(nightMode, c, Color.BLUE, colorized); assertValid(c); } @Test public void testColors_ensureColors_colorized_producesValidPalette_default() { validateColorizedPaletteForColor(Notification.COLOR_DEFAULT); } @Test public void testColors_ensureColors_colorized_producesValidPalette_blue() { validateColorizedPaletteForColor(Color.BLUE); } @Test public void testColors_ensureColors_colorized_producesValidPalette_red() { validateColorizedPaletteForColor(Color.RED); } @Test public void testColors_ensureColors_colorized_producesValidPalette_white() { validateColorizedPaletteForColor(Color.WHITE); } @Test public void testColors_ensureColors_colorized_producesValidPalette_black() { validateColorizedPaletteForColor(Color.BLACK); } public void validateColorizedPaletteForColor(int rawColor) { Notification.Colors cDay = new Notification.Colors(); Notification.Colors cNight = new Notification.Colors(); boolean colorized = true; resolveColorsInNightMode(false, cDay, rawColor, colorized); resolveColorsInNightMode(true, cNight, rawColor, colorized); if (rawColor != Notification.COLOR_DEFAULT) { assertEquals(rawColor, cDay.getBackgroundColor()); assertEquals(rawColor, cNight.getBackgroundColor()); } assertValid(cDay); assertValid(cNight); if (rawColor != Notification.COLOR_DEFAULT) { // When a color is provided, night mode should have no effect on the notification assertEquals(cDay.getBackgroundColor(), cNight.getBackgroundColor()); assertEquals(cDay.getPrimaryTextColor(), cNight.getPrimaryTextColor()); assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor()); assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor()); assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor()); assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor()); assertEquals(cDay.getContrastColor(), cNight.getContrastColor()); assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha()); } } private void assertValid(Notification.Colors c) { // Assert that all colors are populated assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getProtectionColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getPrimaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getRippleAlpha()).isAtLeast(0x00); assertThat(c.getRippleAlpha()).isAtMost(0xff); // Assert that various colors have sufficient contrast assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5); // This accent color is only used for emphasized buttons assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1); } private void assertContrastIsAtLeast(int foreground, int background, double minContrast) { try { assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast); } catch (AssertionError e) { throw new AssertionError( String.format("Insufficient contrast: foreground=#%08x background=#%08x", foreground, background), e); } } private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor, boolean colorized) { runInNightMode(nightMode, () -> c.resolvePalette(mContext, rawColor, colorized, nightMode)); } private void runInNightMode(boolean nightMode, Runnable task) { final String initialNightMode = changeNightMode(nightMode); try { Configuration currentConfig = mContext.getResources().getConfiguration(); boolean isNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; assertEquals(nightMode, isNightMode); task.run(); } finally { runShellCommand("cmd uimode night " + initialNightMode); } } // Change the night mode and return the previous mode private String changeNightMode(boolean nightMode) { final String nightModeText = runShellCommand("cmd uimode night"); final String[] nightModeSplit = nightModeText.split(":"); if (nightModeSplit.length != 2) { fail("Failed to get initial night mode value from " + nightModeText); } String previousMode = nightModeSplit[1].trim(); runShellCommand("cmd uimode night " + (nightMode ? "yes" : "no")); return previousMode; } /** Loading Loading
core/java/android/app/Notification.java +238 −216 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/java/com/android/internal/util/ContrastColorUtil.java +4 −1 Original line number Diff line number Diff line Loading @@ -595,7 +595,9 @@ public class ContrastColorUtil { if (backgroundColor == Notification.COLOR_DEFAULT) { return !defaultBackgroundIsDark; } return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.5; // Color contrast ratio luminance midpoint, X: 1.05 / (X + 0.05) = (X + 0.05) / 0.05 // Solved as X = sqrt(.05 * 1.05) - 0.05 = 0.17912878474 return ColorUtilsFromCompat.calculateLuminance(backgroundColor) > 0.17912878474; } public static double calculateLuminance(int backgroundColor) { Loading @@ -619,6 +621,7 @@ public class ContrastColorUtil { } public static boolean isColorLight(int backgroundColor) { // TODO(b/188947832): Use 0.17912878474 instead of 0.5 to ensure better contrast return calculateLuminance(backgroundColor) > 0.5f; } Loading
core/res/res/values-night/colors.xml +0 −8 Original line number Diff line number Diff line Loading @@ -17,14 +17,6 @@ NOTE: You might also want to edit: packages/SystemUI/res/values-night/colors.xml --> <resources> <!-- The primary text color if the text is on top of a dark background. This is also affects colorized notifications with dark backgrounds. --> <color name="notification_primary_text_color_dark">#ddffffff</color> <!-- The secondary text color if the text is on top of a dark background. --> <color name="notification_secondary_text_color_dark">#b2ffffff</color> <color name="notification_default_color_dark">#ddffffff</color> <color name="notification_primary_text_color_current">@color/notification_primary_text_color_dark</color> <color name="notification_secondary_text_color_current">@color/notification_secondary_text_color_dark</color> Loading
core/tests/coretests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ android_test { "frameworks-core-util-lib", "mockwebserver", "guava", "androidx.core_core", "androidx.test.espresso.core", "androidx.test.ext.junit", "androidx.test.runner", Loading
core/tests/coretests/src/android/app/NotificationTest.java +145 −8 Original line number Diff line number Diff line Loading @@ -16,6 +16,14 @@ package android.app; import static androidx.core.graphics.ColorUtils.calculateContrast; import static com.android.compatibility.common.util.SystemUtil.runShellCommand; import static com.google.common.truth.Truth.assertThat; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; Loading @@ -27,10 +35,10 @@ import android.annotation.Nullable; import android.content.Context; import android.content.Intent; import android.content.LocusId; import android.content.res.Configuration; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; Loading Loading @@ -325,13 +333,142 @@ public class NotificationTest { assertNull(clone.getLocusId()); } private Notification.Builder getMediaNotification() { MediaSession session = new MediaSession(mContext, "test"); return new Notification.Builder(mContext, "color") .setSmallIcon(com.android.internal.R.drawable.emergency_icon) .setContentTitle("Title") .setContentText("Text") .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken())); @Test public void testColors_ensureColors_dayMode_producesValidPalette() { Notification.Colors c = new Notification.Colors(); boolean colorized = false; boolean nightMode = false; resolveColorsInNightMode(nightMode, c, Color.BLUE, colorized); assertValid(c); } @Test public void testColors_ensureColors_nightMode_producesValidPalette() { Notification.Colors c = new Notification.Colors(); boolean colorized = false; boolean nightMode = true; resolveColorsInNightMode(nightMode, c, Color.BLUE, colorized); assertValid(c); } @Test public void testColors_ensureColors_colorized_producesValidPalette_default() { validateColorizedPaletteForColor(Notification.COLOR_DEFAULT); } @Test public void testColors_ensureColors_colorized_producesValidPalette_blue() { validateColorizedPaletteForColor(Color.BLUE); } @Test public void testColors_ensureColors_colorized_producesValidPalette_red() { validateColorizedPaletteForColor(Color.RED); } @Test public void testColors_ensureColors_colorized_producesValidPalette_white() { validateColorizedPaletteForColor(Color.WHITE); } @Test public void testColors_ensureColors_colorized_producesValidPalette_black() { validateColorizedPaletteForColor(Color.BLACK); } public void validateColorizedPaletteForColor(int rawColor) { Notification.Colors cDay = new Notification.Colors(); Notification.Colors cNight = new Notification.Colors(); boolean colorized = true; resolveColorsInNightMode(false, cDay, rawColor, colorized); resolveColorsInNightMode(true, cNight, rawColor, colorized); if (rawColor != Notification.COLOR_DEFAULT) { assertEquals(rawColor, cDay.getBackgroundColor()); assertEquals(rawColor, cNight.getBackgroundColor()); } assertValid(cDay); assertValid(cNight); if (rawColor != Notification.COLOR_DEFAULT) { // When a color is provided, night mode should have no effect on the notification assertEquals(cDay.getBackgroundColor(), cNight.getBackgroundColor()); assertEquals(cDay.getPrimaryTextColor(), cNight.getPrimaryTextColor()); assertEquals(cDay.getSecondaryTextColor(), cNight.getSecondaryTextColor()); assertEquals(cDay.getPrimaryAccentColor(), cNight.getPrimaryAccentColor()); assertEquals(cDay.getSecondaryAccentColor(), cNight.getSecondaryAccentColor()); assertEquals(cDay.getProtectionColor(), cNight.getProtectionColor()); assertEquals(cDay.getContrastColor(), cNight.getContrastColor()); assertEquals(cDay.getRippleAlpha(), cNight.getRippleAlpha()); } } private void assertValid(Notification.Colors c) { // Assert that all colors are populated assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getProtectionColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getPrimaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getSecondaryTextColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getPrimaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getSecondaryAccentColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getErrorColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getContrastColor()).isNotEqualTo(Notification.COLOR_INVALID); assertThat(c.getRippleAlpha()).isAtLeast(0x00); assertThat(c.getRippleAlpha()).isAtMost(0xff); // Assert that various colors have sufficient contrast assertContrastIsAtLeast(c.getPrimaryTextColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getSecondaryTextColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getPrimaryAccentColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getErrorColor(), c.getBackgroundColor(), 4.5); assertContrastIsAtLeast(c.getContrastColor(), c.getBackgroundColor(), 4.5); // This accent color is only used for emphasized buttons assertContrastIsAtLeast(c.getSecondaryAccentColor(), c.getBackgroundColor(), 1); } private void assertContrastIsAtLeast(int foreground, int background, double minContrast) { try { assertThat(calculateContrast(foreground, background)).isAtLeast(minContrast); } catch (AssertionError e) { throw new AssertionError( String.format("Insufficient contrast: foreground=#%08x background=#%08x", foreground, background), e); } } private void resolveColorsInNightMode(boolean nightMode, Notification.Colors c, int rawColor, boolean colorized) { runInNightMode(nightMode, () -> c.resolvePalette(mContext, rawColor, colorized, nightMode)); } private void runInNightMode(boolean nightMode, Runnable task) { final String initialNightMode = changeNightMode(nightMode); try { Configuration currentConfig = mContext.getResources().getConfiguration(); boolean isNightMode = (currentConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; assertEquals(nightMode, isNightMode); task.run(); } finally { runShellCommand("cmd uimode night " + initialNightMode); } } // Change the night mode and return the previous mode private String changeNightMode(boolean nightMode) { final String nightModeText = runShellCommand("cmd uimode night"); final String[] nightModeSplit = nightModeText.split(":"); if (nightModeSplit.length != 2) { fail("Failed to get initial night mode value from " + nightModeText); } String previousMode = nightModeSplit[1].trim(); runShellCommand("cmd uimode night " + (nightMode ? "yes" : "no")); return previousMode; } /** Loading