33eafc6328
Change-Id: I85dc9f76f21ebbe156ef68bcb5a11c28c8d871ba
238 lines
9.3 KiB
Java
238 lines
9.3 KiB
Java
/*
|
|
* Copyright (C) 2017 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3.graphics;
|
|
|
|
import android.app.Notification;
|
|
import android.content.Context;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Color;
|
|
import android.graphics.ColorMatrix;
|
|
import android.graphics.ColorMatrixColorFilter;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
import android.support.v4.graphics.ColorUtils;
|
|
import android.util.Log;
|
|
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.util.Themes;
|
|
|
|
/**
|
|
* 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";
|
|
|
|
private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f;
|
|
private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f;
|
|
|
|
private static IconPalette sBadgePalette;
|
|
private static IconPalette sFolderBadgePalette;
|
|
|
|
public final int dominantColor;
|
|
public final int backgroundColor;
|
|
public final ColorMatrixColorFilter backgroundColorMatrixFilter;
|
|
public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter;
|
|
public final int textColor;
|
|
public final int secondaryColor;
|
|
|
|
private IconPalette(int color, boolean desaturateBackground) {
|
|
dominantColor = color;
|
|
backgroundColor = desaturateBackground ? getMutedColor(dominantColor, 0.87f) : dominantColor;
|
|
ColorMatrix backgroundColorMatrix = new ColorMatrix();
|
|
Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix);
|
|
backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
|
|
if (!desaturateBackground) {
|
|
saturatedBackgroundColorMatrixFilter = backgroundColorMatrixFilter;
|
|
} else {
|
|
// Get slightly more saturated background color.
|
|
Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix);
|
|
saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix);
|
|
}
|
|
textColor = getTextColorForBackground(backgroundColor);
|
|
secondaryColor = getLowContrastColor(backgroundColor);
|
|
}
|
|
|
|
/**
|
|
* Returns a color suitable for the progress bar color of preload icon.
|
|
*/
|
|
public int getPreloadProgressColor(Context context) {
|
|
int result = dominantColor;
|
|
|
|
// Make sure that the dominant color has enough saturation to be visible properly.
|
|
float[] hsv = new float[3];
|
|
Color.colorToHSV(result, hsv);
|
|
if (hsv[1] < MIN_PRELOAD_COLOR_SATURATION) {
|
|
result = Themes.getColorAccent(context);
|
|
} else {
|
|
hsv[2] = Math.max(MIN_PRELOAD_COLOR_LIGHTNESS, hsv[2]);
|
|
result = Color.HSVToColor(hsv);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static IconPalette fromDominantColor(int dominantColor, boolean desaturateBackground) {
|
|
return new IconPalette(dominantColor, desaturateBackground);
|
|
}
|
|
|
|
/**
|
|
* Returns an IconPalette based on the badge_color in colors.xml.
|
|
* If that color is Color.TRANSPARENT, then returns null instead.
|
|
*/
|
|
public static @Nullable IconPalette getBadgePalette(Resources resources) {
|
|
int badgeColor = resources.getColor(R.color.badge_color);
|
|
if (badgeColor == Color.TRANSPARENT) {
|
|
// Colors will be extracted per app icon, so a static palette won't work.
|
|
return null;
|
|
}
|
|
if (sBadgePalette == null) {
|
|
sBadgePalette = fromDominantColor(badgeColor, false);
|
|
}
|
|
return sBadgePalette;
|
|
}
|
|
|
|
/**
|
|
* Returns an IconPalette based on the folder_badge_color in colors.xml.
|
|
*/
|
|
public static @NonNull IconPalette getFolderBadgePalette(Resources resources) {
|
|
if (sFolderBadgePalette == null) {
|
|
int badgeColor = resources.getColor(R.color.folder_badge_color);
|
|
sFolderBadgePalette = fromDominantColor(badgeColor, false);
|
|
}
|
|
return sFolderBadgePalette;
|
|
}
|
|
|
|
/**
|
|
* 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, 4.5);
|
|
}
|
|
/**
|
|
* Finds a suitable color such that there's enough contrast.
|
|
*
|
|
* @param fg the color to start searching from.
|
|
* @param bg the color to ensure contrast against.
|
|
* @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 fg, int bg, double minRatio) {
|
|
if (ColorUtils.calculateContrast(fg, bg) >= minRatio) {
|
|
return fg;
|
|
}
|
|
|
|
double[] lab = new double[3];
|
|
ColorUtils.colorToLAB(bg, lab);
|
|
double bgL = lab[0];
|
|
ColorUtils.colorToLAB(fg, lab);
|
|
double fgL = lab[0];
|
|
boolean isBgDark = bgL < 50;
|
|
|
|
double low = isBgDark ? fgL : 0, high = isBgDark ? 100 : fgL;
|
|
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;
|
|
fg = ColorUtils.LABToColor(l, a, b);
|
|
if (ColorUtils.calculateContrast(fg, bg) > minRatio) {
|
|
if (isBgDark) high = l; else low = l;
|
|
} else {
|
|
if (isBgDark) low = l; else high = l;
|
|
}
|
|
}
|
|
return ColorUtils.LABToColor(low, a, b);
|
|
}
|
|
|
|
private static int getMutedColor(int color, float whiteScrimAlpha) {
|
|
int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha));
|
|
return ColorUtils.compositeColors(whiteScrim, color);
|
|
}
|
|
|
|
private static int getTextColorForBackground(int backgroundColor) {
|
|
return getLighterOrDarkerVersionOfColor(backgroundColor, 4.5f);
|
|
}
|
|
|
|
private static int getLowContrastColor(int color) {
|
|
return getLighterOrDarkerVersionOfColor(color, 1.5f);
|
|
}
|
|
|
|
private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) {
|
|
int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio);
|
|
int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio);
|
|
int translucentWhiteOrBlack;
|
|
if (whiteMinAlpha >= 0) {
|
|
translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha);
|
|
} else if (blackMinAlpha >= 0) {
|
|
translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha);
|
|
} else {
|
|
translucentWhiteOrBlack = Color.WHITE;
|
|
}
|
|
return ColorUtils.compositeColors(translucentWhiteOrBlack, color);
|
|
}
|
|
}
|