diff options
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/Notification.java | 126 |
1 files changed, 95 insertions, 31 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index e39636dc45fb..719025f9e215 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1884,6 +1884,14 @@ public class Notification implements Parcelable * clicks. To launch an activity in those cases, provide a {@link PendingIntent} for the * activity itself. * + * <p>How an Action is displayed, including whether the {@code icon}, {@code text}, or + * both are displayed or required, depends on where and how the action is used, and the + * {@link Style} applied to the Notification. + * + * <p>When the {@code title} is a {@link android.text.Spanned}, any colors set by a + * {@link ForegroundColorSpan} or {@link TextAppearanceSpan} may be removed or displayed + * with an altered in luminance to ensure proper contrast within the Notification. + * * @param icon icon to show for this action * @param title the title of the action * @param intent the {@link PendingIntent} to fire when users trigger this action @@ -6121,21 +6129,22 @@ public class Notification implements Parcelable if (emphasizedMode) { // change the background bgColor CharSequence title = action.title; - ColorStateList[] outResultColor = new ColorStateList[1]; int buttonFillColor = getColors(p).getSecondaryAccentColor(); if (isLegacy()) { title = ContrastColorUtil.clearColorSpans(title); } else { - int notifBackgroundColor = getColors(p).getBackgroundColor(); - title = ensureColorSpanContrast(title, notifBackgroundColor, outResultColor); + // Check for a full-length span color to use as the button fill color. + Integer fullLengthColor = getFullLengthSpanColor(title); + if (fullLengthColor != null) { + // Ensure the custom button fill has 1.3:1 contrast w/ notification bg. + int notifBackgroundColor = getColors(p).getBackgroundColor(); + buttonFillColor = ensureButtonFillContrast( + fullLengthColor, notifBackgroundColor); + } + // Remove full-length color spans and ensure text contrast with the button fill. + title = ensureColorSpanContrast(title, buttonFillColor); } button.setTextViewText(R.id.action0, processTextSpans(title)); - boolean hasColorOverride = outResultColor[0] != null; - if (hasColorOverride) { - // There's a span spanning the full text, let's take it and use it as the - // background color - buttonFillColor = outResultColor[0].getDefaultColor(); - } final int textColor = ContrastColorUtil.resolvePrimaryColor(mContext, buttonFillColor, mInNightMode); button.setTextColor(R.id.action0, textColor); @@ -6168,17 +6177,58 @@ public class Notification implements Parcelable } /** - * Ensures contrast on color spans against a background color. also returns the color of the - * text if a span was found that spans over the whole text. + * Extract the color from a full-length span from the text. + * + * @param charSequence the charSequence containing spans + * @return the raw color of the text's last full-length span containing a color, or null if + * no full-length span sets the text color. + * @hide + */ + @VisibleForTesting + @Nullable + public static Integer getFullLengthSpanColor(CharSequence charSequence) { + // NOTE: this method preserves the functionality that for a CharSequence with multiple + // full-length spans, the color of the last one is used. + Integer result = null; + if (charSequence instanceof Spanned) { + Spanned ss = (Spanned) charSequence; + Object[] spans = ss.getSpans(0, ss.length(), Object.class); + // First read through all full-length spans to get the button fill color, which will + // be used as the background color for ensuring contrast of non-full-length spans. + for (Object span : spans) { + int spanStart = ss.getSpanStart(span); + int spanEnd = ss.getSpanEnd(span); + boolean fullLength = (spanEnd - spanStart) == charSequence.length(); + if (!fullLength) { + continue; + } + if (span instanceof TextAppearanceSpan) { + TextAppearanceSpan originalSpan = (TextAppearanceSpan) span; + ColorStateList textColor = originalSpan.getTextColor(); + if (textColor != null) { + result = textColor.getDefaultColor(); + } + } else if (span instanceof ForegroundColorSpan) { + ForegroundColorSpan originalSpan = (ForegroundColorSpan) span; + result = originalSpan.getForegroundColor(); + } + } + } + return result; + } + + /** + * Ensures contrast on color spans against a background color. + * Note that any full-length color spans will be removed instead of being contrasted. * * @param charSequence the charSequence on which the spans are * @param background the background color to ensure the contrast against - * @param outResultColor an array in which a color will be returned as the first element if - * there exists a full length color span. * @return the contrasted charSequence + * @hide */ - private static CharSequence ensureColorSpanContrast(CharSequence charSequence, - int background, ColorStateList[] outResultColor) { + @VisibleForTesting + public static CharSequence ensureColorSpanContrast(CharSequence charSequence, + int background) { if (charSequence instanceof Spanned) { Spanned ss = (Spanned) charSequence; Object[] spans = ss.getSpans(0, ss.length(), Object.class); @@ -6195,19 +6245,19 @@ public class Notification implements Parcelable TextAppearanceSpan originalSpan = (TextAppearanceSpan) resultSpan; ColorStateList textColor = originalSpan.getTextColor(); if (textColor != null) { - int[] colors = textColor.getColors(); - int[] newColors = new int[colors.length]; - for (int i = 0; i < newColors.length; i++) { - boolean isBgDark = isColorDark(background); - newColors[i] = ContrastColorUtil.ensureLargeTextContrast( - colors[i], background, isBgDark); - } - textColor = new ColorStateList(textColor.getStates().clone(), - newColors); if (fullLength) { - outResultColor[0] = textColor; // Let's drop the color from the span textColor = null; + } else { + int[] colors = textColor.getColors(); + int[] newColors = new int[colors.length]; + for (int i = 0; i < newColors.length; i++) { + boolean isBgDark = isColorDark(background); + newColors[i] = ContrastColorUtil.ensureLargeTextContrast( + colors[i], background, isBgDark); + } + textColor = new ColorStateList(textColor.getStates().clone(), + newColors); } resultSpan = new TextAppearanceSpan( originalSpan.getFamily(), @@ -6217,15 +6267,14 @@ public class Notification implements Parcelable originalSpan.getLinkTextColor()); } } else if (resultSpan instanceof ForegroundColorSpan) { - ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; - int foregroundColor = originalSpan.getForegroundColor(); - boolean isBgDark = isColorDark(background); - foregroundColor = ContrastColorUtil.ensureLargeTextContrast( - foregroundColor, background, isBgDark); if (fullLength) { - outResultColor[0] = ColorStateList.valueOf(foregroundColor); resultSpan = null; } else { + ForegroundColorSpan originalSpan = (ForegroundColorSpan) resultSpan; + int foregroundColor = originalSpan.getForegroundColor(); + boolean isBgDark = isColorDark(background); + foregroundColor = ContrastColorUtil.ensureLargeTextContrast( + foregroundColor, background, isBgDark); resultSpan = new ForegroundColorSpan(foregroundColor); } } else { @@ -6255,6 +6304,21 @@ public class Notification implements Parcelable } /** + * Finds a button fill color with sufficient contrast over bg (1.3:1) that has the same hue + * as the original color, but is lightened or darkened depending on whether the background + * is dark or light. + * + * @hide + */ + @VisibleForTesting + public static int ensureButtonFillContrast(int color, int bg) { + return isColorDark(bg) + ? ContrastColorUtil.findContrastColorAgainstDark(color, bg, true, 1.3) + : ContrastColorUtil.findContrastColor(color, bg, true, 1.3); + } + + + /** * @return Whether we are currently building a notification from a legacy (an app that * doesn't create material notifications by itself) app. */ |
