Bug 1290014 - Move code from the FaviconGenerator class to the IconGenerator in the icons package. r=ahunt,Grisha

The FaviconGenerator is the last class in the old favicons package. As this class is only used by the IconGenerator
let's move the code inside the new class and remove FaviconGenerator.

MozReview-Commit-ID: 7NsJRGdoUWv

--HG--
extra : rebase_source : 703b80560f47220d094de0049cd9b92f8cd524eb
This commit is contained in:
Sebastian Kaspari 2016-08-17 18:08:51 +02:00
parent 2a9ce08987
commit fb80b68a08
6 changed files with 225 additions and 275 deletions

View File

@ -1,168 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.favicons;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import org.mozilla.gecko.R;
import org.mozilla.gecko.util.ThreadUtils;
import java.util.Arrays;
import java.util.Locale;
/**
* Generate favicons based on the page URL.
*/
public class FaviconGenerator {
// Mozilla's Visual Design Colour Palette
// http://firefoxux.github.io/StyleGuide/#/visualDesign/colours
private static final int[] COLORS = {
0xFFc33c32,
0xFFf25820,
0xFFff9216,
0xFFffcb00,
0xFF57bd35,
0xFF01bdad,
0xFF0996f8,
0xFF02538b,
0xFF1f386e,
0xFF7a2f7a,
0xFFea385e,
};
// List of common prefixes of host names. Those prefixes will be striped before a prepresentative
// character for an URL is determined.
private static final String[] COMMON_PREFIXES = {
"www.",
"m.",
"mobile.",
};
private static final int TEXT_SIZE_DP = 12;
public static class IconWithColor {
public final Bitmap bitmap;
public final int color;
private IconWithColor(Bitmap bitmap, int color) {
this.bitmap = bitmap;
this.color = color;
}
}
/**
* Generate default favicon for the given page URL.
*/
public static IconWithColor generate(Context context, String pageURL) {
final Resources resources = context.getResources();
final int widthAndHeight = resources.getDimensionPixelSize(R.dimen.favicon_bg);
final int roundedCorners = resources.getDimensionPixelOffset(R.dimen.favicon_corner_radius);
final Bitmap favicon = Bitmap.createBitmap(widthAndHeight, widthAndHeight, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(favicon);
final int color = pickColor(pageURL);
final Paint paint = new Paint();
paint.setColor(color);
canvas.drawRoundRect(new RectF(0, 0, widthAndHeight, widthAndHeight), roundedCorners, roundedCorners, paint);
paint.setColor(Color.WHITE);
final String character = getRepresentativeCharacter(pageURL);
final float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DP, context.getResources().getDisplayMetrics());
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);
paint.setAntiAlias(true);
canvas.drawText(character,
canvas.getWidth() / 2,
(int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)),
paint);
return new IconWithColor(favicon, color);
}
/**
* Get a representative character for the given URL.
*
* For example this method will return "f" for "http://m.facebook.com/foobar".
*/
protected static String getRepresentativeCharacter(String url) {
if (TextUtils.isEmpty(url)) {
return "?";
}
final String snippet = getRepresentativeSnippet(url);
for (int i = 0; i < snippet.length(); i++) {
char c = snippet.charAt(i);
if (Character.isLetterOrDigit(c)) {
return String.valueOf(Character.toUpperCase(c));
}
}
// Nothing found..
return "?";
}
/**
* Return a color for this URL. Colors will be based on the host. URLs with the same host will
* return the same color.
*/
protected static int pickColor(String url) {
if (TextUtils.isEmpty(url)) {
return COLORS[0];
}
final String snippet = getRepresentativeSnippet(url);
final int color = Math.abs(snippet.hashCode() % COLORS.length);
return COLORS[color];
}
/**
* Get the representative part of the URL. Usually this is the host (without common prefixes).
*/
private static String getRepresentativeSnippet(@NonNull String url) {
Uri uri = Uri.parse(url);
// Use the host if available
String snippet = uri.getHost();
if (TextUtils.isEmpty(snippet)) {
// If the uri does not have a host (e.g. file:// uris) then use the path
snippet = uri.getPath();
}
if (TextUtils.isEmpty(snippet)) {
// If we still have no snippet then just return the question mark
return "?";
}
// Strip common prefixes that we do not want to use to determine the representative character
for (String prefix : COMMON_PREFIXES) {
if (snippet.startsWith(prefix)) {
snippet = snippet.substring(prefix.length());
}
}
return snippet;
}
}

View File

@ -5,7 +5,20 @@
package org.mozilla.gecko.icons.loader;
import org.mozilla.gecko.favicons.FaviconGenerator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import android.util.TypedValue;
import org.mozilla.gecko.R;
import org.mozilla.gecko.icons.IconRequest;
import org.mozilla.gecko.icons.IconResponse;
@ -14,6 +27,31 @@ import org.mozilla.gecko.icons.IconResponse;
* to be the last loader that will be tried.
*/
public class IconGenerator implements IconLoader {
// Mozilla's Visual Design Colour Palette
// http://firefoxux.github.io/StyleGuide/#/visualDesign/colours
private static final int[] COLORS = {
0xFFc33c32,
0xFFf25820,
0xFFff9216,
0xFFffcb00,
0xFF57bd35,
0xFF01bdad,
0xFF0996f8,
0xFF02538b,
0xFF1f386e,
0xFF7a2f7a,
0xFFea385e,
};
// List of common prefixes of host names. Those prefixes will be striped before a prepresentative
// character for an URL is determined.
private static final String[] COMMON_PREFIXES = {
"www.",
"m.",
"mobile.",
};
private static final int TEXT_SIZE_DP = 12;
@Override
public IconResponse load(IconRequest request) {
if (request.getIconCount() > 1) {
@ -22,9 +60,109 @@ public class IconGenerator implements IconLoader {
return null;
}
final FaviconGenerator.IconWithColor iconWithColor = FaviconGenerator.generate(
request.getContext(), request.getPageUrl());
return generate(request.getContext(), request.getPageUrl());
}
return IconResponse.createGenerated(iconWithColor.bitmap, iconWithColor.color);
/**
* Generate default favicon for the given page URL.
*/
@VisibleForTesting static IconResponse generate(Context context, String pageURL) {
final Resources resources = context.getResources();
final int widthAndHeight = resources.getDimensionPixelSize(R.dimen.favicon_bg);
final int roundedCorners = resources.getDimensionPixelOffset(R.dimen.favicon_corner_radius);
final Bitmap favicon = Bitmap.createBitmap(widthAndHeight, widthAndHeight, Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(favicon);
final int color = pickColor(pageURL);
final Paint paint = new Paint();
paint.setColor(color);
canvas.drawRoundRect(new RectF(0, 0, widthAndHeight, widthAndHeight), roundedCorners, roundedCorners, paint);
paint.setColor(Color.WHITE);
final String character = getRepresentativeCharacter(pageURL);
final float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, TEXT_SIZE_DP, context.getResources().getDisplayMetrics());
paint.setTextAlign(Paint.Align.CENTER);
paint.setTextSize(textSize);
paint.setAntiAlias(true);
canvas.drawText(character,
canvas.getWidth() / 2,
(int) ((canvas.getHeight() / 2) - ((paint.descent() + paint.ascent()) / 2)),
paint);
return IconResponse.createGenerated(favicon, color);
}
/**
* Get a representative character for the given URL.
*
* For example this method will return "f" for "http://m.facebook.com/foobar".
*/
@VisibleForTesting static String getRepresentativeCharacter(String url) {
if (TextUtils.isEmpty(url)) {
return "?";
}
final String snippet = getRepresentativeSnippet(url);
for (int i = 0; i < snippet.length(); i++) {
char c = snippet.charAt(i);
if (Character.isLetterOrDigit(c)) {
return String.valueOf(Character.toUpperCase(c));
}
}
// Nothing found..
return "?";
}
/**
* Return a color for this URL. Colors will be based on the host. URLs with the same host will
* return the same color.
*/
@VisibleForTesting static int pickColor(String url) {
if (TextUtils.isEmpty(url)) {
return COLORS[0];
}
final String snippet = getRepresentativeSnippet(url);
final int color = Math.abs(snippet.hashCode() % COLORS.length);
return COLORS[color];
}
/**
* Get the representative part of the URL. Usually this is the host (without common prefixes).
*/
private static String getRepresentativeSnippet(@NonNull String url) {
Uri uri = Uri.parse(url);
// Use the host if available
String snippet = uri.getHost();
if (TextUtils.isEmpty(snippet)) {
// If the uri does not have a host (e.g. file:// uris) then use the path
snippet = uri.getPath();
}
if (TextUtils.isEmpty(snippet)) {
// If we still have no snippet then just return the question mark
return "?";
}
// Strip common prefixes that we do not want to use to determine the representative character
for (String prefix : COMMON_PREFIXES) {
if (snippet.startsWith(prefix)) {
snippet = snippet.substring(prefix.length());
}
}
return snippet;
}
}

View File

@ -6,23 +6,18 @@
package org.mozilla.gecko.widget;
import org.mozilla.gecko.R;
import org.mozilla.gecko.favicons.FaviconGenerator;
import org.mozilla.gecko.icons.IconCallback;
import org.mozilla.gecko.icons.IconResponse;
import org.mozilla.gecko.util.ThreadUtils;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import java.lang.ref.WeakReference;

View File

@ -375,11 +375,6 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'DynamicToolbar.java',
'EditBookmarkDialog.java',
'Experiments.java',
'favicons/decoders/FaviconDecoder.java',
'favicons/decoders/ICODecoder.java',
'favicons/decoders/IconDirectoryEntry.java',
'favicons/decoders/LoadFaviconResult.java',
'favicons/FaviconGenerator.java',
'feeds/action/CheckForUpdatesAction.java',
'feeds/action/EnrollSubscriptionsAction.java',
'feeds/action/FeedAction.java',
@ -492,6 +487,10 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'home/TopSitesThumbnailView.java',
'home/TwoLinePageRow.java',
'home/UpdateViewFaviconLoadedListener.java',
'icons/decoders/FaviconDecoder.java',
'icons/decoders/ICODecoder.java',
'icons/decoders/IconDirectoryEntry.java',
'icons/decoders/LoadFaviconResult.java',
'icons/IconCallback.java',
'icons/IconDescriptor.java',
'icons/IconDescriptorComparator.java',

View File

@ -1,93 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.favicons;
import android.graphics.Bitmap;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.robolectric.RuntimeEnvironment;
@RunWith(TestRunner.class)
public class TestFaviconGenerator {
@Test
public void testRepresentativeCharacter() {
Assert.assertEquals("M", FaviconGenerator.getRepresentativeCharacter("https://mozilla.org"));
Assert.assertEquals("W", FaviconGenerator.getRepresentativeCharacter("http://wikipedia.org"));
Assert.assertEquals("P", FaviconGenerator.getRepresentativeCharacter("http://plus.google.com"));
Assert.assertEquals("E", FaviconGenerator.getRepresentativeCharacter("https://en.m.wikipedia.org/wiki/Main_Page"));
// Stripping common prefixes
Assert.assertEquals("T", FaviconGenerator.getRepresentativeCharacter("http://www.theverge.com"));
Assert.assertEquals("F", FaviconGenerator.getRepresentativeCharacter("https://m.facebook.com"));
Assert.assertEquals("T", FaviconGenerator.getRepresentativeCharacter("https://mobile.twitter.com"));
// Special urls
Assert.assertEquals("?", FaviconGenerator.getRepresentativeCharacter("file:///"));
Assert.assertEquals("S", FaviconGenerator.getRepresentativeCharacter("file:///system/"));
Assert.assertEquals("P", FaviconGenerator.getRepresentativeCharacter("ftp://people.mozilla.org/test"));
// No values
Assert.assertEquals("?", FaviconGenerator.getRepresentativeCharacter(""));
Assert.assertEquals("?", FaviconGenerator.getRepresentativeCharacter(null));
// Rubbish
Assert.assertEquals("Z", FaviconGenerator.getRepresentativeCharacter("zZz"));
Assert.assertEquals("Ö", FaviconGenerator.getRepresentativeCharacter("ölkfdpou3rkjaslfdköasdfo8"));
Assert.assertEquals("?", FaviconGenerator.getRepresentativeCharacter("_*+*'##"));
Assert.assertEquals("", FaviconGenerator.getRepresentativeCharacter("¯\\_(ツ)_/¯"));
Assert.assertEquals("", FaviconGenerator.getRepresentativeCharacter("ಠ_ಠ Look of Disapproval"));
// Non-ASCII
Assert.assertEquals("Ä", FaviconGenerator.getRepresentativeCharacter("http://www.ätzend.de"));
Assert.assertEquals("", FaviconGenerator.getRepresentativeCharacter("http://名がドメイン.com"));
Assert.assertEquals("C", FaviconGenerator.getRepresentativeCharacter("http://√.com"));
Assert.assertEquals("ß", FaviconGenerator.getRepresentativeCharacter("http://ß.de"));
Assert.assertEquals("Ԛ", FaviconGenerator.getRepresentativeCharacter("http://ԛәлп.com/")); // cyrillic
// Punycode
Assert.assertEquals("X", FaviconGenerator.getRepresentativeCharacter("http://xn--tzend-fra.de")); // ätzend.de
Assert.assertEquals("X", FaviconGenerator.getRepresentativeCharacter("http://xn--V8jxj3d1dzdz08w.com")); // 名がドメイン.com
// Numbers
Assert.assertEquals("1", FaviconGenerator.getRepresentativeCharacter("https://www.1and1.com/"));
// IP
Assert.assertEquals("1", FaviconGenerator.getRepresentativeCharacter("https://192.168.0.1"));
}
@Test
public void testPickColor() {
final int color = FaviconGenerator.pickColor("http://m.facebook.com");
// Color does not change
for (int i = 0; i < 100; i++) {
Assert.assertEquals(color, FaviconGenerator.pickColor("http://m.facebook.com"));
}
// Color is stable for "similar" hosts.
Assert.assertEquals(color, FaviconGenerator.pickColor("https://m.facebook.com"));
Assert.assertEquals(color, FaviconGenerator.pickColor("http://facebook.com"));
Assert.assertEquals(color, FaviconGenerator.pickColor("http://www.facebook.com"));
Assert.assertEquals(color, FaviconGenerator.pickColor("http://www.facebook.com/foo/bar/foobar?mobile=1"));
}
@Test
public void testGeneratingFavicon() {
final FaviconGenerator.IconWithColor iconWithColor = FaviconGenerator.generate(RuntimeEnvironment.application, "http://m.facebook.com");
final Bitmap bitmap = iconWithColor.bitmap;
Assert.assertNotNull(bitmap);
final int size = RuntimeEnvironment.application.getResources().getDimensionPixelSize(R.dimen.favicon_bg);
Assert.assertEquals(size, bitmap.getWidth());
Assert.assertEquals(size, bitmap.getHeight());
Assert.assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
}
}

View File

@ -3,10 +3,13 @@
package org.mozilla.gecko.icons.loader;
import android.graphics.Bitmap;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.icons.IconDescriptor;
import org.mozilla.gecko.icons.IconRequest;
@ -46,4 +49,80 @@ public class TestIconGenerator {
Assert.assertNotNull(response);
Assert.assertNotNull(response.getBitmap());
}
@Test
public void testRepresentativeCharacter() {
Assert.assertEquals("M", IconGenerator.getRepresentativeCharacter("https://mozilla.org"));
Assert.assertEquals("W", IconGenerator.getRepresentativeCharacter("http://wikipedia.org"));
Assert.assertEquals("P", IconGenerator.getRepresentativeCharacter("http://plus.google.com"));
Assert.assertEquals("E", IconGenerator.getRepresentativeCharacter("https://en.m.wikipedia.org/wiki/Main_Page"));
// Stripping common prefixes
Assert.assertEquals("T", IconGenerator.getRepresentativeCharacter("http://www.theverge.com"));
Assert.assertEquals("F", IconGenerator.getRepresentativeCharacter("https://m.facebook.com"));
Assert.assertEquals("T", IconGenerator.getRepresentativeCharacter("https://mobile.twitter.com"));
// Special urls
Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter("file:///"));
Assert.assertEquals("S", IconGenerator.getRepresentativeCharacter("file:///system/"));
Assert.assertEquals("P", IconGenerator.getRepresentativeCharacter("ftp://people.mozilla.org/test"));
// No values
Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(""));
Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter(null));
// Rubbish
Assert.assertEquals("Z", IconGenerator.getRepresentativeCharacter("zZz"));
Assert.assertEquals("Ö", IconGenerator.getRepresentativeCharacter("ölkfdpou3rkjaslfdköasdfo8"));
Assert.assertEquals("?", IconGenerator.getRepresentativeCharacter("_*+*'##"));
Assert.assertEquals("", IconGenerator.getRepresentativeCharacter("¯\\_(ツ)_/¯"));
Assert.assertEquals("", IconGenerator.getRepresentativeCharacter("ಠ_ಠ Look of Disapproval"));
// Non-ASCII
Assert.assertEquals("Ä", IconGenerator.getRepresentativeCharacter("http://www.ätzend.de"));
Assert.assertEquals("", IconGenerator.getRepresentativeCharacter("http://名がドメイン.com"));
Assert.assertEquals("C", IconGenerator.getRepresentativeCharacter("http://√.com"));
Assert.assertEquals("ß", IconGenerator.getRepresentativeCharacter("http://ß.de"));
Assert.assertEquals("Ԛ", IconGenerator.getRepresentativeCharacter("http://ԛәлп.com/")); // cyrillic
// Punycode
Assert.assertEquals("X", IconGenerator.getRepresentativeCharacter("http://xn--tzend-fra.de")); // ätzend.de
Assert.assertEquals("X", IconGenerator.getRepresentativeCharacter("http://xn--V8jxj3d1dzdz08w.com")); // 名がドメイン.com
// Numbers
Assert.assertEquals("1", IconGenerator.getRepresentativeCharacter("https://www.1and1.com/"));
// IP
Assert.assertEquals("1", IconGenerator.getRepresentativeCharacter("https://192.168.0.1"));
}
@Test
public void testPickColor() {
final int color = IconGenerator.pickColor("http://m.facebook.com");
// Color does not change
for (int i = 0; i < 100; i++) {
Assert.assertEquals(color, IconGenerator.pickColor("http://m.facebook.com"));
}
// Color is stable for "similar" hosts.
Assert.assertEquals(color, IconGenerator.pickColor("https://m.facebook.com"));
Assert.assertEquals(color, IconGenerator.pickColor("http://facebook.com"));
Assert.assertEquals(color, IconGenerator.pickColor("http://www.facebook.com"));
Assert.assertEquals(color, IconGenerator.pickColor("http://www.facebook.com/foo/bar/foobar?mobile=1"));
}
@Test
public void testGeneratingFavicon() {
final IconResponse response = IconGenerator.generate(RuntimeEnvironment.application, "http://m.facebook.com");
final Bitmap bitmap = response.getBitmap();
Assert.assertNotNull(bitmap);
final int size = RuntimeEnvironment.application.getResources().getDimensionPixelSize(R.dimen.favicon_bg);
Assert.assertEquals(size, bitmap.getWidth());
Assert.assertEquals(size, bitmap.getHeight());
Assert.assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
}
}