Bug 1290014 - Move code from the FaviconGenerator class to the IconGenerator in the icons package. r=ahunt,Grisha
authorSebastian Kaspari <s.kaspari@gmail.com>
Wed, 17 Aug 2016 18:08:51 +0200
changeset 312471 9b852e0472ff46f4e8a4c0888fd7d2925c001efd
parent 312470 c588732a368ebce977fd6730bcb73672529807cd
child 312472 a87ed908d84b5d3a6d3a8dbed171608ff4d6b33c
push id20447
push userkwierso@gmail.com
push dateFri, 02 Sep 2016 20:36:44 +0000
treeherderfx-team@969397f22187 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersahunt, Grisha
bugs1290014
milestone51.0a1
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
mobile/android/base/java/org/mozilla/gecko/favicons/FaviconGenerator.java
mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
mobile/android/base/moz.build
mobile/android/tests/background/junit4/src/org/mozilla/gecko/favicons/TestFaviconGenerator.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconGenerator.java
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/favicons/FaviconGenerator.java
+++ /dev/null
@@ -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;
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/icons/loader/IconGenerator.java
@@ -1,30 +1,168 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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.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;
 
 /**
  * This loader will generate an icon in case no icon could be loaded. In order to do so this needs
  * 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) {
             // There are still other icons to try. We will only generate an icon if there's only one
             // icon left and all previous loaders have failed (assuming this is the last one).
             return null;
         }
 
-        final FaviconGenerator.IconWithColor iconWithColor = FaviconGenerator.generate(
-                request.getContext(), request.getPageUrl());
+        return generate(request.getContext(), request.getPageUrl());
+    }
+
+    /**
+     * 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 "?";
+        }
 
-        return IconResponse.createGenerated(iconWithColor.bitmap, iconWithColor.color);
+        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;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/FaviconView.java
@@ -1,33 +1,28 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
 /* 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.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;
 
 /**
  * Special version of ImageView for favicons.
  * Displays solid colour background around Favicon to fill space not occupied by the icon. Colour
  * selected is the dominant colour of the provided Favicon.
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -370,21 +370,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'dlc/StudyAction.java',
     'dlc/SyncAction.java',
     'dlc/VerifyAction.java',
     'DoorHangerPopup.java',
     'DownloadsIntegration.java',
     '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',
     'feeds/action/SetupAlarmsAction.java',
     'feeds/action/SubscribeToFeedAction.java',
     'feeds/action/WithdrawSubscriptionsAction.java',
     'feeds/ContentNotificationsDelegate.java',
     'feeds/FeedAlarmReceiver.java',
@@ -487,16 +482,20 @@ gbjar.sources += ['java/org/mozilla/geck
     'home/TabMenuStrip.java',
     'home/TabMenuStripLayout.java',
     'home/TopSitesGridItemView.java',
     'home/TopSitesGridView.java',
     'home/TopSitesPanel.java',
     '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',
     'icons/IconRequest.java',
     'icons/IconRequestBuilder.java',
     'icons/IconRequestExecutor.java',
     'icons/IconResponse.java',
     'icons/Icons.java',
deleted file mode 100644
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/favicons/TestFaviconGenerator.java
+++ /dev/null
@@ -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());
-    }
-}
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconGenerator.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/icons/loader/TestIconGenerator.java
@@ -1,17 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 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;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.icons.Icons;
 import org.robolectric.RuntimeEnvironment;
 
 @RunWith(TestRunner.class)
@@ -41,9 +44,85 @@ public class TestIconGenerator {
                 .build();
 
         IconLoader loader = new IconGenerator();
         IconResponse response = loader.load(request);
 
         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());
+    }
 }