Bug 1320775 - Use bundled touch-tiles as favicons for suggested sites r=sebastian

There are a number of ways in which we could supply favicons for the default suggested
sites. Reusing the touch tiles has the advantage that it works for both our own suggested
sites, and also distribution-supplied suggested sites. If we were to add yet another
icon source, distribution supplied sites would end up having no nice icon in AS topsites.

The priority ordering of the SuggestedSitePreparer means icons will be overriden as soon
as a site-supplied favicon is available - these icons will only be used up until the point
where a site has been visited.

MozReview-Commit-ID: CHsinHHpfnw

--HG--
extra : rebase_source : a162f5b15e968f382b43505290b0633cbe6e2c7a
This commit is contained in:
Andrzej Hunt 2017-02-08 15:15:12 -08:00
parent 7411323bbc
commit e50e97366d
6 changed files with 203 additions and 1 deletions

View File

@ -14,7 +14,7 @@ import android.support.annotation.VisibleForTesting;
* A class describing the location and properties of an icon that can be loaded.
*/
public class IconDescriptor {
@IntDef({ TYPE_GENERIC, TYPE_FAVICON, TYPE_TOUCHICON, TYPE_LOOKUP })
@IntDef({ TYPE_GENERIC, TYPE_FAVICON, TYPE_TOUCHICON, TYPE_LOOKUP, TYPE_BUNDLED_TILE })
@interface IconType {}
// The type values are used for ranking icons (higher values = try to load first).
@ -22,6 +22,7 @@ public class IconDescriptor {
@VisibleForTesting static final int TYPE_LOOKUP = 1;
@VisibleForTesting static final int TYPE_FAVICON = 5;
@VisibleForTesting static final int TYPE_TOUCHICON = 10;
@VisibleForTesting static final int TYPE_BUNDLED_TILE = 15;
private final String url;
private final int size;
@ -58,6 +59,16 @@ public class IconDescriptor {
return new IconDescriptor(TYPE_LOOKUP, url, 0, null);
}
/**
* Create a bundled tile icon at the given URL. MIME type or size is not known until we load
* the icons, but we know these icons are high fidelity. (Although the icons are png's at time
* of writing, they could be changed to webp or VectorDrawable in future.)
*/
public static IconDescriptor createBundledTileIcon(@NonNull String url) {
return new IconDescriptor(TYPE_BUNDLED_TILE, url, 0, null);
}
private IconDescriptor(@IconType int type, @NonNull String url, int size, String mimeType) {
this.type = type;
this.url = url;

View File

@ -16,6 +16,7 @@ import org.mozilla.gecko.icons.loader.IconLoader;
import org.mozilla.gecko.icons.loader.JarLoader;
import org.mozilla.gecko.icons.loader.LegacyLoader;
import org.mozilla.gecko.icons.loader.MemoryLoader;
import org.mozilla.gecko.icons.loader.SuggestedSiteLoader;
import org.mozilla.gecko.icons.preparation.AboutPagesPreparer;
import org.mozilla.gecko.icons.preparation.AddDefaultIconUrl;
import org.mozilla.gecko.icons.preparation.FilterKnownFailureUrls;
@ -23,6 +24,7 @@ import org.mozilla.gecko.icons.preparation.FilterMimeTypes;
import org.mozilla.gecko.icons.preparation.FilterPrivilegedUrls;
import org.mozilla.gecko.icons.preparation.LookupIconUrl;
import org.mozilla.gecko.icons.preparation.Preparer;
import org.mozilla.gecko.icons.preparation.SuggestedSitePreparer;
import org.mozilla.gecko.icons.processing.ColorProcessor;
import org.mozilla.gecko.icons.processing.DiskProcessor;
import org.mozilla.gecko.icons.processing.MemoryProcessor;
@ -67,6 +69,11 @@ import java.util.concurrent.TimeUnit;
// URLs. We always want to be able to load those specific icons.
new AboutPagesPreparer(),
// Suggested sites have icons bundled in the app - we should use them until the user has
// visited a specified page (after which the standard icon lookup will generally provide
// an update icon.
new SuggestedSitePreparer(),
// Add the default favicon URL (*/favicon.ico) to the list of icon URLs; with a low priority,
// this icon URL should be tried last.
new AddDefaultIconUrl(),
@ -94,6 +101,9 @@ import java.util.concurrent.TimeUnit;
// Try to load the icon from the disk cache.
new DiskLoader(),
// Try to load from the suggested site tile builder
new SuggestedSiteLoader(),
// If the icon is not in any of our cashes and can't be decoded then look into the
// database (legacy). Maybe this icon was loaded before the new code was deployed.
new LegacyLoader(),

View File

@ -0,0 +1,107 @@
/* -*- 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.icons.loader;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.db.SuggestedSites;
import org.mozilla.gecko.home.ImageLoader;
import org.mozilla.gecko.icons.IconRequest;
import org.mozilla.gecko.icons.IconResponse;
import java.io.IOException;
public class SuggestedSiteLoader implements IconLoader {
public static final String SUGGESTED_SITE_TOUCHTILE = "suggestedsitetile:";
private static final String LOGTAG = SuggestedSiteLoader.class.getSimpleName();
@Nullable
@Override
public IconResponse load(IconRequest request) {
if (request.shouldSkipDisk()) {
return null;
}
final String iconUrl = request.getBestIcon().getUrl();
if (iconUrl.startsWith(SUGGESTED_SITE_TOUCHTILE)) {
return buildIcon(request.getContext(), iconUrl.substring(SUGGESTED_SITE_TOUCHTILE.length()), request.getTargetSize());
}
return null;
}
@VisibleForTesting
Bitmap loadBitmap(final Context context, final String iconURL) throws IOException {
return ImageLoader.with(context)
.load(iconURL)
.noFade()
.get();
}
private IconResponse buildIcon(final Context context, final String siteURL, final int targetSize) {
final SuggestedSites suggestedSites = BrowserDB.from(context).getSuggestedSites();
final String iconLocation = suggestedSites.getImageUrlForUrl(siteURL);
final String backgroundColorString = suggestedSites.getBackgroundColorForUrl(siteURL);
if (iconLocation == null || backgroundColorString == null) {
// There's little we can do if loading a bundled resource fails: this failure could
// be caused by a distribution (as opposed to Gecko), so we should just shout loudly,
// as opposed to crashing:
Log.e(LOGTAG, "Unable to find tile data definitions for site:" + siteURL);
}
final int backgroundColor = Color.parseColor(backgroundColorString);
try {
final Bitmap foreground = loadBitmap(context, iconLocation);
// Our supplied tile icons are bigger than the max favicon size. They are also not square,,
// so we scale them down and fit them into a square (i.e. centering in their smaller dimension):
final Bitmap output = Bitmap.createBitmap(targetSize, targetSize, foreground.getConfig());
final Canvas canvas = new Canvas(output);
canvas.drawColor(backgroundColor);
final Rect src = new Rect(0, 0, foreground.getWidth(), foreground.getHeight());
// And we draw the icon in the middle of that square:
final float scaleFactor = targetSize * 1.0f / Math.max(foreground.getHeight(), foreground.getWidth());
final int heightDelta = targetSize - (int) (scaleFactor * foreground.getHeight());
final int widthDelta = targetSize - (int) (scaleFactor * foreground.getWidth());
final Rect dst = new Rect(widthDelta / 2, heightDelta / 2, output.getWidth() - (widthDelta / 2), output.getHeight() - (heightDelta / 2));
// Interpolate when painting:
final Paint paint = new Paint();
paint.setFilterBitmap(true);
paint.setAntiAlias(true);
paint.setDither(true);
canvas.drawBitmap(foreground, src, dst, paint);
final IconResponse response = IconResponse.create(output);
response.updateColor(backgroundColor);
return response;
} catch (IOException e) {
// Same as above: if we can't load the data, shout - but don't crash:
Log.e(LOGTAG, "Unable to load tile data for site:" + siteURL + " at location:" + iconLocation);
}
return null;
}
}

View File

@ -0,0 +1,62 @@
/* -*- 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.icons.preparation;
import android.content.Context;
import android.database.Cursor;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.icons.IconDescriptor;
import org.mozilla.gecko.icons.IconRequest;
import org.mozilla.gecko.icons.loader.SuggestedSiteLoader;
import java.util.HashSet;
import java.util.Set;
public class SuggestedSitePreparer implements Preparer {
private boolean initialised = false;
private final Set<String> siteFaviconMap = new HashSet<>();
// Loading suggested sites (and iterating over them) is potentially slow. The number of suggested
// sites is low, and a HashSet containing them is therefore likely to be exceedingly small.
// Hence we opt to iterate over the list once, and do an immediate lookup every time a favicon
// is requested:
private void initialise(final Context context) {
final Cursor cursor = BrowserDB.from(context).getSuggestedSites().get(Integer.MAX_VALUE);
try {
final int urlColumnIndex = cursor.getColumnIndexOrThrow(BrowserContract.Bookmarks.URL);
while (cursor.moveToNext()) {
final String url = cursor.getString(urlColumnIndex);
siteFaviconMap.add(url);
}
} finally {
cursor.close();
}
}
@Override
public void prepare(final IconRequest request) {
if (request.shouldSkipDisk()) {
return;
}
if (!initialised) {
initialise(request.getContext());
initialised = true;
}
final String siteURL = request.getPageUrl();
if (siteFaviconMap.contains(siteURL)) {
request.modify()
.icon(IconDescriptor.createBundledTileIcon(SuggestedSiteLoader.SUGGESTED_SITE_TOUCHTILE + request.getPageUrl()))
.deferBuild();
}
}
}

View File

@ -560,6 +560,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'icons/loader/JarLoader.java',
'icons/loader/LegacyLoader.java',
'icons/loader/MemoryLoader.java',
'icons/loader/SuggestedSiteLoader.java',
'icons/preparation/AboutPagesPreparer.java',
'icons/preparation/AddDefaultIconUrl.java',
'icons/preparation/FilterKnownFailureUrls.java',
@ -567,6 +568,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'icons/preparation/FilterPrivilegedUrls.java',
'icons/preparation/LookupIconUrl.java',
'icons/preparation/Preparer.java',
'icons/preparation/SuggestedSitePreparer.java',
'icons/processing/ColorProcessor.java',
'icons/processing/DiskProcessor.java',
'icons/processing/MemoryProcessor.java',

View File

@ -53,4 +53,14 @@ public class TestIconDescriptor {
Assert.assertEquals(0, descriptor.getSize());
Assert.assertEquals(IconDescriptor.TYPE_LOOKUP, descriptor.getType());
}
@Test
public void testBundledTileIconDescriptor() {
final IconDescriptor descriptor = IconDescriptor.createBundledTileIcon(ICON_URL);
Assert.assertEquals(ICON_URL, descriptor.getUrl());
Assert.assertNull(descriptor.getMimeType());
Assert.assertEquals(0, descriptor.getSize());
Assert.assertEquals(IconDescriptor.TYPE_BUNDLED_TILE, descriptor.getType());
}
}