Merge "Ensure notification icons have enough contrast with background." into ub-launcher3-master

This commit is contained in:
Tony Wickham
2017-01-27 23:34:19 +00:00
committed by Android (Google) Code Review
6 changed files with 150 additions and 18 deletions
+2
View File
@@ -50,4 +50,6 @@
<!-- Used as a fallback since colorSecondary doesn't exist pre-API 25 -->
<color name="fallback_secondary_color">#FF37474F</color>
<color name="notification_icon_default_color">#757575</color> <!-- Gray 600 -->
</resources>
@@ -16,14 +16,22 @@
package com.android.launcher3.graphics;
import android.app.Notification;
import android.content.Context;
import android.graphics.Color;
import android.support.v4.graphics.ColorUtils;
import android.util.Log;
import com.android.launcher3.R;
/**
* Contains colors based on the dominant color of an icon.
*/
public class IconPalette {
private static final boolean DEBUG = false;
private static final String TAG = "IconPalette";
public int backgroundColor;
public int textColor;
public int secondaryColor;
@@ -36,6 +44,100 @@ public class IconPalette {
return palette;
}
/**
* Resolves a color such that it has enough contrast to be used as the
* color of an icon or text on the given background color.
*
* @return a color of the same hue with enough contrast against the background.
*
* This was copied from com.android.internal.util.NotificationColorUtil.
*/
public static int resolveContrastColor(Context context, int color, int background) {
final int resolvedColor = resolveColor(context, color);
int contrastingColor = ensureTextContrast(resolvedColor, background);
if (contrastingColor != resolvedColor) {
if (DEBUG){
Log.w(TAG, String.format(
"Enhanced contrast of notification for %s " +
"%s (over background) by changing #%s to %s",
context.getPackageName(),
contrastChange(resolvedColor, contrastingColor, background),
Integer.toHexString(resolvedColor), Integer.toHexString(contrastingColor)));
}
}
return contrastingColor;
}
/**
* Resolves {@param color} to an actual color if it is {@link Notification#COLOR_DEFAULT}
*
* This was copied from com.android.internal.util.NotificationColorUtil.
*/
private static int resolveColor(Context context, int color) {
if (color == Notification.COLOR_DEFAULT) {
return context.getColor(R.color.notification_icon_default_color);
}
return color;
}
/** For debugging. This was copied from com.android.internal.util.NotificationColorUtil. */
private static String contrastChange(int colorOld, int colorNew, int bg) {
return String.format("from %.2f:1 to %.2f:1",
ColorUtils.calculateContrast(colorOld, bg),
ColorUtils.calculateContrast(colorNew, bg));
}
/**
* Finds a text color with sufficient contrast over bg that has the same hue as the original
* color.
*
* This was copied from com.android.internal.util.NotificationColorUtil.
*/
private static int ensureTextContrast(int color, int bg) {
return findContrastColor(color, bg, true, 4.5);
}
/**
* Finds a suitable color such that there's enough contrast.
*
* @param color the color to start searching from.
* @param other the color to ensure contrast against. Assumed to be lighter than {@param color}
* @param findFg if true, we assume {@param color} is a foreground, otherwise a background.
* @param minRatio the minimum contrast ratio required.
* @return a color with the same hue as {@param color}, potentially darkened to meet the
* contrast ratio.
*
* This was copied from com.android.internal.util.NotificationColorUtil.
*/
private static int findContrastColor(int color, int other, boolean findFg, double minRatio) {
int fg = findFg ? color : other;
int bg = findFg ? other : color;
if (ColorUtils.calculateContrast(fg, bg) >= minRatio) {
return color;
}
double[] lab = new double[3];
ColorUtils.colorToLAB(findFg ? fg : bg, lab);
double low = 0, high = lab[0];
final double a = lab[1], b = lab[2];
for (int i = 0; i < 15 && high - low > 0.00001; i++) {
final double l = (low + high) / 2;
if (findFg) {
fg = ColorUtils.LABToColor(l, a, b);
} else {
bg = ColorUtils.LABToColor(l, a, b);
}
if (ColorUtils.calculateContrast(fg, bg) > minRatio) {
low = l;
} else {
high = l;
}
}
return ColorUtils.LABToColor(low, a, b);
}
private static int getMutedColor(int color) {
int alpha = (int) (255 * 0.15f);
return ColorUtils.compositeColors(ColorUtils.setAlphaComponent(color, alpha), Color.WHITE);
@@ -61,6 +61,7 @@ public class NotificationFooterLayout extends LinearLayout {
LinearLayout.LayoutParams mIconLayoutParams;
private LinearLayout mIconRow;
private int mBackgroundColor;
private int mTextColor;
public NotificationFooterLayout(Context context) {
@@ -90,7 +91,8 @@ public class NotificationFooterLayout extends LinearLayout {
}
public void applyColors(IconPalette iconPalette) {
setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
mBackgroundColor = iconPalette.backgroundColor;
setBackgroundTintList(ColorStateList.valueOf(mBackgroundColor));
findViewById(R.id.divider).setBackgroundColor(iconPalette.secondaryColor);
mTextColor = iconPalette.textColor;
}
@@ -130,7 +132,7 @@ public class NotificationFooterLayout extends LinearLayout {
private void addNotificationIconForInfo(NotificationInfo info, boolean fromOverflow) {
View icon = new View(getContext());
icon.setBackground(info.iconDrawable);
icon.setBackground(info.getIconForBackground(getContext(), mBackgroundColor));
icon.setOnClickListener(info);
int addIndex = mIconRow.getChildCount();
if (fromOverflow) {
@@ -25,6 +25,7 @@ import android.service.notification.StatusBarNotification;
import android.view.View;
import com.android.launcher3.Launcher;
import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.popup.PopupContainerWithArrow;
import com.android.launcher3.util.PackageUserKey;
@@ -41,11 +42,14 @@ public class NotificationInfo implements View.OnClickListener {
public final String notificationKey;
public final CharSequence title;
public final CharSequence text;
public final Drawable iconDrawable;
public final PendingIntent intent;
public final boolean autoCancel;
public final boolean dismissable;
private final Drawable mIconDrawable;
private boolean mShouldTintIcon;
private int mIconColor;
/**
* Extracts the data that we need from the StatusBarNotification.
*/
@@ -60,10 +64,12 @@ public class NotificationInfo implements View.OnClickListener {
Icon icon = notification.getLargeIcon();
if (icon == null) {
icon = notification.getSmallIcon();
iconDrawable = icon.loadDrawable(context);
iconDrawable.setTint(statusBarNotification.getNotification().color);
mIconDrawable = icon.loadDrawable(context);
mIconColor = statusBarNotification.getNotification().color;
mShouldTintIcon = true;
} else {
iconDrawable = icon.loadDrawable(context);
mIconDrawable = icon.loadDrawable(context);
mShouldTintIcon = false;
}
intent = notification.contentIntent;
autoCancel = (notification.flags & Notification.FLAG_AUTO_CANCEL) != 0;
@@ -83,4 +89,18 @@ public class NotificationInfo implements View.OnClickListener {
}
PopupContainerWithArrow.getOpen(launcher).close(true);
}
public Drawable getIconForBackground(Context context, int background) {
if (!mShouldTintIcon) {
return mIconDrawable;
}
mIconColor = IconPalette.resolveContrastColor(context, mIconColor, background);
Drawable icon = mIconDrawable.mutate();
// DrawableContainer ignores the color filter if it's already set, so clear it first to
// get it set and invalidated properly.
icon.setTintList(null);
icon.setTint(mIconColor);
mShouldTintIcon = false;
return icon;
}
}
@@ -35,7 +35,9 @@ import com.android.launcher3.graphics.IconPalette;
import com.android.launcher3.popup.PopupItemView;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.android.launcher3.LauncherAnimUtils.animateViewHeight;
@@ -117,7 +119,7 @@ public class NotificationItemView extends PopupItemView {
mHeader.setBackgroundTintList(ColorStateList.valueOf(iconPalette.backgroundColor));
mHeader.setTextColor(ColorStateList.valueOf(iconPalette.textColor));
mDivider.setBackgroundColor(iconPalette.secondaryColor);
mMainView.setBackgroundColor(iconPalette.backgroundColor);
mMainView.applyColors(iconPalette);
mFooter.applyColors(iconPalette);
}
@@ -135,7 +137,7 @@ public class NotificationItemView extends PopupItemView {
@Override
public void onIconAnimationEnd(NotificationInfo newMainNotification) {
if (newMainNotification != null) {
mMainView.applyNotificationInfo(newMainNotification, mIconView, mIconPalette);
mMainView.applyNotificationInfo(newMainNotification, mIconView, true);
// Remove the animated notification from the footer by calling trim
// TODO: Remove the notification in NotificationFooterLayout directly
// instead of relying on this hack.
@@ -41,6 +41,7 @@ public class NotificationMainView extends LinearLayout implements SwipeHelper.Ca
private NotificationInfo mNotificationInfo;
private TextView mTitleView;
private TextView mTextView;
private IconPalette mIconPalette;
public NotificationMainView(Context context) {
this(context, null, 0);
@@ -62,35 +63,38 @@ public class NotificationMainView extends LinearLayout implements SwipeHelper.Ca
mTextView = (TextView) findViewById(R.id.text);
}
public void applyColors(IconPalette iconPalette) {
setBackgroundColor(iconPalette.backgroundColor);
mIconPalette = iconPalette;
}
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView) {
applyNotificationInfo(mainNotification, iconView, null);
applyNotificationInfo(mainNotification, iconView, false);
}
/**
* @param iconPalette if not null, indicates that the new info should be animated in,
* and that part of this animation includes animating the background
* from iconPalette.secondaryColor to iconPalette.backgroundColor.
* Sets the content of this view, animating it after a new icon shifts up if necessary.
*/
public void applyNotificationInfo(NotificationInfo mainNotification, View iconView,
@Nullable IconPalette iconPalette) {
boolean animate = iconPalette != null;
boolean animate) {
if (animate) {
mTitleView.setAlpha(0);
mTextView.setAlpha(0);
setBackgroundColor(iconPalette.secondaryColor);
setBackgroundColor(mIconPalette.secondaryColor);
}
mNotificationInfo = mainNotification;
mTitleView.setText(mNotificationInfo.title);
mTextView.setText(mNotificationInfo.text);
iconView.setBackground(mNotificationInfo.iconDrawable);
iconView.setBackground(mNotificationInfo.getIconForBackground(
getContext(), mIconPalette.backgroundColor));
setOnClickListener(mNotificationInfo);
setTranslationX(0);
if (animate) {
AnimatorSet animation = LauncherAnimUtils.createAnimatorSet();
Animator textFade = new LauncherViewPropertyAnimator(mTextView).alpha(1);
Animator titleFade = new LauncherViewPropertyAnimator(mTitleView).alpha(1);
ValueAnimator colorChange = ValueAnimator.ofArgb(iconPalette.secondaryColor,
iconPalette.backgroundColor);
ValueAnimator colorChange = ValueAnimator.ofArgb(mIconPalette.secondaryColor,
mIconPalette.backgroundColor);
colorChange.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {