Merge "Ensure notification icons have enough contrast with background." into ub-launcher3-master
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user