1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 package org.mozilla.gecko.icons.loader;
6 
7 import android.content.Context;
8 import android.graphics.Bitmap;
9 import android.graphics.Canvas;
10 import android.graphics.Color;
11 import android.graphics.Paint;
12 import android.graphics.Rect;
13 import android.support.annotation.Nullable;
14 import android.support.annotation.VisibleForTesting;
15 import android.util.Log;
16 
17 import org.mozilla.gecko.db.BrowserDB;
18 import org.mozilla.gecko.db.SuggestedSites;
19 import org.mozilla.gecko.home.ImageLoader;
20 import org.mozilla.gecko.icons.IconRequest;
21 import org.mozilla.gecko.icons.IconResponse;
22 
23 import java.io.IOException;
24 
25 public class SuggestedSiteLoader implements IconLoader {
26     public static final String SUGGESTED_SITE_TOUCHTILE = "suggestedsitetile:";
27 
28     private static final String LOGTAG = SuggestedSiteLoader.class.getSimpleName();
29 
30     @Nullable
31     @Override
load(IconRequest request)32     public IconResponse load(IconRequest request) {
33         if (request.shouldSkipDisk()) {
34             return null;
35         }
36 
37         final String iconUrl = request.getBestIcon().getUrl();
38 
39         if (iconUrl.startsWith(SUGGESTED_SITE_TOUCHTILE)) {
40             return buildIcon(request.getContext(), iconUrl.substring(SUGGESTED_SITE_TOUCHTILE.length()), request.getTargetSize());
41         }
42 
43         return null;
44     }
45 
46     @VisibleForTesting
loadBitmap(final Context context, final String iconURL)47     Bitmap loadBitmap(final Context context, final String iconURL) throws IOException {
48         return ImageLoader.with(context)
49                 .load(iconURL)
50                 .noFade()
51                 .get();
52     }
53 
buildIcon(final Context context, final String siteURL, final int targetSize)54     private IconResponse buildIcon(final Context context, final String siteURL, final int targetSize) {
55         final SuggestedSites suggestedSites = BrowserDB.from(context).getSuggestedSites();
56 
57         if (suggestedSites == null) {
58             // See longer explanation in SuggestedSitePreparer: suggested sites aren't always loaded.
59             // If they aren't, SuggestedSitePreparer won't suggest any bundled icons so we should
60             // never try to load them anyway, but we should double check here to be completely safe.
61             return null;
62         }
63 
64         final String iconLocation = suggestedSites.getImageUrlForUrl(siteURL);
65         final String backgroundColorString = suggestedSites.getBackgroundColorForUrl(siteURL);
66 
67         if (iconLocation == null || backgroundColorString == null) {
68             // There's little we can do if loading a bundled resource fails: this failure could
69             // be caused by a distribution (as opposed to Gecko), so we should just shout loudly,
70             // as opposed to crashing:
71             Log.e(LOGTAG, "Unable to find tile data definitions for site:" + siteURL);
72             return null;
73         }
74 
75         final int backgroundColor = Color.parseColor(backgroundColorString);
76 
77         try {
78             final Bitmap foreground = loadBitmap(context, iconLocation);
79 
80             // Our supplied tile icons are bigger than the max favicon size. They are also not square,,
81             // so we scale them down and fit them into a square (i.e. centering in their smaller dimension):
82             final Bitmap output = Bitmap.createBitmap(targetSize, targetSize, foreground.getConfig());
83             final Canvas canvas = new Canvas(output);
84             canvas.drawColor(backgroundColor);
85 
86             final Rect src = new Rect(0, 0, foreground.getWidth(), foreground.getHeight());
87 
88             // And we draw the icon in the middle of that square:
89             final float scaleFactor = targetSize * 1.0f / Math.max(foreground.getHeight(), foreground.getWidth());
90 
91             final int heightDelta = targetSize - (int) (scaleFactor * foreground.getHeight());
92             final int widthDelta = targetSize - (int) (scaleFactor * foreground.getWidth());
93 
94             final Rect dst = new Rect(widthDelta / 2, heightDelta / 2, output.getWidth() - (widthDelta / 2), output.getHeight() - (heightDelta / 2));
95 
96             // Interpolate when painting:
97             final Paint paint = new Paint();
98             paint.setFilterBitmap(true);
99             paint.setAntiAlias(true);
100             paint.setDither(true);
101 
102             canvas.drawBitmap(foreground, src, dst, paint);
103 
104             final IconResponse response = IconResponse.create(output);
105             response.updateColor(backgroundColor);
106 
107             return response;
108         } catch (IOException e) {
109             // Same as above: if we can't load the data, shout - but don't crash:
110             Log.e(LOGTAG, "Unable to load tile data for site:" + siteURL + " at location:" + iconLocation);
111         }
112 
113         return null;
114     }
115 }
116