Merge changes I858017b8,Ife32b600 into oc-dev

* changes:
  Improved the media color extraction algorithm
  Fixed broken coretests
This commit is contained in:
Selim Cinek
2017-05-17 15:59:03 +00:00
committed by Android (Google) Code Review
6 changed files with 159 additions and 100 deletions

View File

@@ -169,4 +169,8 @@ public class ImageFloatingTextView extends TextView {
}
return false;
}
public int getLayoutHeight() {
return getLayout().getHeight();
}
}

View File

@@ -138,7 +138,7 @@ public class MessagingLinearLayout extends ViewGroup {
first = false;
boolean measuredTooSmall = false;
if (textChild != null) {
measuredTooSmall = childHeight < textChild.getLayout().getHeight()
measuredTooSmall = childHeight < textChild.getLayoutHeight()
+ textChild.getPaddingTop() + textChild.getPaddingBottom();
}

View File

@@ -19,7 +19,6 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:maxHeight="300px"
android:spacing="5px">
</com.android.internal.widget.MessagingLinearLayout>

View File

@@ -16,7 +16,7 @@
package com.android.internal.widget;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
@@ -111,6 +111,9 @@ public class ImageFloatingTextViewTest {
mTextView.measure(widthMeasureSpec, heightMeasureSpec);
mView.measure(widthMeasureSpec, heightMeasureSpec);
assertEquals(mTextView.getMeasuredHeight(), mView.getMeasuredHeight());
// We're at most allowed to be the same height as the regular textview and maybe a bit
// smaller since our layout snaps to full textlines.
assertTrue("The measured view should never be taller then the normal textview!",
mView.getMeasuredHeight() <= mTextView.getMeasuredHeight());
}
}

View File

@@ -21,7 +21,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.os.Debug;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.core.deps.guava.base.Function;
import android.support.test.filters.SmallTest;
@@ -46,8 +45,7 @@ public class MessagingLinearLayoutTest {
@Before
public void setup() {
mContext = InstrumentationRegistry.getTargetContext();
// maxHeight: 300px
// spacing: 50px
// spacing: 5px
mView = (MessagingLinearLayout) LayoutInflater.from(mContext).inflate(
R.layout.messaging_linear_layout_test, null);
}
@@ -81,8 +79,8 @@ public class MessagingLinearLayoutTest {
assertEquals(3, child1.getNumIndentLines());
assertEquals(0, child2.getNumIndentLines());
assertFalse(child1.isHidden());
assertFalse(child2.isHidden());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(205, mView.getMeasuredHeight());
}
@@ -100,8 +98,8 @@ public class MessagingLinearLayoutTest {
assertEquals(2, child1.getNumIndentLines());
assertEquals(1, child2.getNumIndentLines());
assertFalse(child1.isHidden());
assertFalse(child2.isHidden());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(105, mView.getMeasuredHeight());
}
@@ -118,14 +116,14 @@ public class MessagingLinearLayoutTest {
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
assertEquals(3, child2.getNumIndentLines());
assertTrue(child1.isHidden());
assertFalse(child2.isHidden());
assertEquals(300, mView.getMeasuredHeight());
assertTrue("child1 should be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(350, mView.getMeasuredHeight());
}
@Test
public void testLargeSmall_largeWrapsWith3indentbutnot3_andHitsMax() {
FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 5 : 4);
public void testLargeSmall_largeWrapsWith3indentbutNotFullHeight_andHitsMax() {
FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 7 : 6);
FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
mView.setNumIndentLines(2);
@@ -135,10 +133,11 @@ public class MessagingLinearLayoutTest {
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
assertTrue(child1.isHidden());
assertFalse(child2.isHidden());
assertEquals(50, mView.getMeasuredHeight());
assertEquals(2, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(355, mView.getMeasuredHeight());
assertEquals(3, child1.getNumIndentLines());
assertEquals(0, child2.getNumIndentLines());
}
@Test
@@ -153,8 +152,8 @@ public class MessagingLinearLayoutTest {
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
assertFalse(child1.isHidden());
assertFalse(child2.isHidden());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(255, mView.getMeasuredHeight());
assertEquals(3, child1.getNumIndentLines());
assertEquals(0, child2.getNumIndentLines());
@@ -183,11 +182,24 @@ public class MessagingLinearLayoutTest {
return mNumIndentLines;
}
@Override
public int getLayoutHeight() {
return Math.max(LINE_HEIGHT, getMeasuredHeight());
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(500, widthMeasureSpec),
resolveSize(getDesiredHeight(), heightMeasureSpec));
clampToMultiplesOfLineHeight(resolveSize(getDesiredHeight(),
heightMeasureSpec)));
}
private int clampToMultiplesOfLineHeight(int size) {
if (size <= LINE_HEIGHT) {
return size;
}
return (size / LINE_HEIGHT) * LINE_HEIGHT;
}
@Override

View File

@@ -24,7 +24,6 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.support.annotation.VisibleForTesting;
import android.support.v4.graphics.ColorUtils;
import android.support.v7.graphics.Palette;
import android.util.LayoutDirection;
@@ -41,10 +40,31 @@ public class MediaNotificationProcessor {
/**
* The fraction below which we select the vibrant instead of the light/dark vibrant color
*/
private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 0.75f;
private static final float POPULATION_FRACTION_FOR_MORE_VIBRANT = 1.0f;
/**
* Minimum saturation that a muted color must have if there exists if deciding between two
* colors
*/
private static final float MIN_SATURATION_WHEN_DECIDING = 0.19f;
/**
* Minimum fraction that any color must have to be picked up as a text color
*/
private static final double MINIMUM_IMAGE_FRACTION = 0.002;
/**
* The population fraction to select the dominant color as the text color over a the colored
* ones.
*/
private static final float POPULATION_FRACTION_FOR_DOMINANT = 0.01f;
/**
* The population fraction to select a white or black color as the background over a color.
*/
private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
private static final float BLACK_MAX_LIGHTNESS = 0.08f;
private static final float WHITE_MIN_LIGHTNESS = 0.92f;
private static final float WHITE_MIN_LIGHTNESS = 0.90f;
private static final int RESIZE_BITMAP_AREA = 150 * 150;
private final ImageGradientColorizer mColorizer;
private final Context mContext;
@@ -109,8 +129,11 @@ public class MediaNotificationProcessor {
.resizeBitmapArea(RESIZE_BITMAP_AREA);
Palette palette = paletteBuilder.generate();
backgroundColor = findBackgroundColorAndFilter(palette);
// we want the full region again
paletteBuilder.setRegion(0, 0, bitmap.getWidth(), bitmap.getHeight());
// we want most of the full region again, slightly shifted to the right
float textColorStartWidthFraction = 0.4f;
paletteBuilder.setRegion((int) (bitmap.getWidth() * textColorStartWidthFraction), 0,
bitmap.getWidth(),
bitmap.getHeight());
if (mFilteredBackgroundHsl != null) {
paletteBuilder.addFilter((rgb, hsl) -> {
// at least 10 degrees hue difference
@@ -120,78 +143,7 @@ public class MediaNotificationProcessor {
}
paletteBuilder.addFilter(mBlackWhiteFilter);
palette = paletteBuilder.generate();
int foregroundColor;
if (NotificationColorUtil.isColorLight(backgroundColor)) {
Palette.Swatch first = palette.getDarkVibrantSwatch();
Palette.Swatch second = palette.getVibrantSwatch();
if (first != null && second != null) {
int firstPopulation = first.getPopulation();
int secondPopulation = second.getPopulation();
if (firstPopulation / secondPopulation
< POPULATION_FRACTION_FOR_MORE_VIBRANT) {
foregroundColor = second.getRgb();
} else {
foregroundColor = first.getRgb();
}
} else if (first != null) {
foregroundColor = first.getRgb();
} else if (second != null) {
foregroundColor = second.getRgb();
} else {
first = palette.getMutedSwatch();
second = palette.getDarkMutedSwatch();
if (first != null && second != null) {
float firstSaturation = first.getHsl()[1];
float secondSaturation = second.getHsl()[1];
if (firstSaturation > secondSaturation) {
foregroundColor = first.getRgb();
} else {
foregroundColor = second.getRgb();
}
} else if (first != null) {
foregroundColor = first.getRgb();
} else if (second != null) {
foregroundColor = second.getRgb();
} else {
foregroundColor = Color.BLACK;
}
}
} else {
Palette.Swatch first = palette.getLightVibrantSwatch();
Palette.Swatch second = palette.getVibrantSwatch();
if (first != null && second != null) {
int firstPopulation = first.getPopulation();
int secondPopulation = second.getPopulation();
if (firstPopulation / secondPopulation
< POPULATION_FRACTION_FOR_MORE_VIBRANT) {
foregroundColor = second.getRgb();
} else {
foregroundColor = first.getRgb();
}
} else if (first != null) {
foregroundColor = first.getRgb();
} else if (second != null) {
foregroundColor = second.getRgb();
} else {
first = palette.getMutedSwatch();
second = palette.getLightMutedSwatch();
if (first != null && second != null) {
float firstSaturation = first.getHsl()[1];
float secondSaturation = second.getHsl()[1];
if (firstSaturation > secondSaturation) {
foregroundColor = first.getRgb();
} else {
foregroundColor = second.getRgb();
}
} else if (first != null) {
foregroundColor = first.getRgb();
} else if (second != null) {
foregroundColor = second.getRgb();
} else {
foregroundColor = Color.WHITE;
}
}
}
int foregroundColor = selectForegroundColor(backgroundColor, palette);
builder.setColorPalette(backgroundColor, foregroundColor);
} else {
int id = mIsLowPriority
@@ -206,6 +158,95 @@ public class MediaNotificationProcessor {
}
}
private int selectForegroundColor(int backgroundColor, Palette palette) {
if (NotificationColorUtil.isColorLight(backgroundColor)) {
return selectForegroundColorForSwatches(palette.getDarkVibrantSwatch(),
palette.getVibrantSwatch(),
palette.getDarkMutedSwatch(),
palette.getMutedSwatch(),
palette.getDominantSwatch(),
Color.BLACK);
} else {
return selectForegroundColorForSwatches(palette.getLightVibrantSwatch(),
palette.getVibrantSwatch(),
palette.getLightMutedSwatch(),
palette.getMutedSwatch(),
palette.getDominantSwatch(),
Color.WHITE);
}
}
private int selectForegroundColorForSwatches(Palette.Swatch moreVibrant,
Palette.Swatch vibrant, Palette.Swatch moreMutedSwatch, Palette.Swatch mutedSwatch,
Palette.Swatch dominantSwatch, int fallbackColor) {
Palette.Swatch coloredCandidate = selectVibrantCandidate(moreVibrant, vibrant);
if (coloredCandidate == null) {
coloredCandidate = selectMutedCandidate(mutedSwatch, moreMutedSwatch);
}
if (coloredCandidate != null) {
if (dominantSwatch == coloredCandidate) {
return coloredCandidate.getRgb();
} else if ((float) coloredCandidate.getPopulation() / dominantSwatch.getPopulation()
< POPULATION_FRACTION_FOR_DOMINANT
&& dominantSwatch.getHsl()[1] > MIN_SATURATION_WHEN_DECIDING) {
return dominantSwatch.getRgb();
} else {
return coloredCandidate.getRgb();
}
} else if (hasEnoughPopulation(dominantSwatch)) {
return dominantSwatch.getRgb();
} else {
return fallbackColor;
}
}
private Palette.Swatch selectMutedCandidate(Palette.Swatch first,
Palette.Swatch second) {
boolean firstValid = hasEnoughPopulation(first);
boolean secondValid = hasEnoughPopulation(second);
if (firstValid && secondValid) {
float firstSaturation = first.getHsl()[1];
float secondSaturation = second.getHsl()[1];
float populationFraction = first.getPopulation() / (float) second.getPopulation();
if (firstSaturation * populationFraction > secondSaturation) {
return first;
} else {
return second;
}
} else if (firstValid) {
return first;
} else if (secondValid) {
return second;
}
return null;
}
private Palette.Swatch selectVibrantCandidate(Palette.Swatch first, Palette.Swatch second) {
boolean firstValid = hasEnoughPopulation(first);
boolean secondValid = hasEnoughPopulation(second);
if (firstValid && secondValid) {
int firstPopulation = first.getPopulation();
int secondPopulation = second.getPopulation();
if (firstPopulation / (float) secondPopulation
< POPULATION_FRACTION_FOR_MORE_VIBRANT) {
return second;
} else {
return first;
}
} else if (firstValid) {
return first;
} else if (secondValid) {
return second;
}
return null;
}
private boolean hasEnoughPopulation(Palette.Swatch swatch) {
// We want a fraction that is at least 1% of the image
return swatch != null
&& (swatch.getPopulation() / (float) RESIZE_BITMAP_AREA > MINIMUM_IMAGE_FRACTION);
}
private int findBackgroundColorAndFilter(Palette palette) {
// by default we use the dominant palette
Palette.Swatch dominantSwatch = palette.getDominantSwatch();