Bug 888326 - Part 3: Factor the inner classes of Favicon into their own files. r=mleibovic

This commit is contained in:
Chris Kitching 2013-09-12 10:49:36 -04:00
parent 752da87dd8
commit 1968180312
6 changed files with 243 additions and 209 deletions

View File

@ -9,6 +9,8 @@ import org.mozilla.gecko.animation.PropertyAnimator;
import org.mozilla.gecko.db.BrowserContract.Combined;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.favicons.LoadFaviconTask;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
@ -706,7 +708,7 @@ abstract public class BrowserApp extends GeckoApp
}
Favicons.loadFavicon(url, tab.getFaviconURL(), 0,
new Favicons.OnFaviconLoadedListener() {
new OnFaviconLoadedListener() {
@Override
public void onFaviconLoaded(String url, Bitmap favicon) {
GeckoAppShell.createShortcut(title, url, url, favicon == null ? null : favicon, "");
@ -1332,9 +1334,9 @@ abstract public class BrowserApp extends GeckoApp
private void loadFavicon(final Tab tab) {
maybeCancelFaviconLoad(tab);
int flags = Favicons.FLAG_SCALE | ( (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : Favicons.FLAG_PERSIST);
int flags = LoadFaviconTask.FLAG_SCALE | ( (tab.isPrivate() || tab.getErrorType() != Tab.ErrorType.NONE) ? 0 : LoadFaviconTask.FLAG_PERSIST);
long id = Favicons.loadFavicon(tab.getURL(), tab.getFaviconURL(), flags,
new Favicons.OnFaviconLoadedListener() {
new OnFaviconLoadedListener() {
@Override
public void onFaviconLoaded(String pageUrl, Bitmap favicon) {

View File

@ -77,6 +77,8 @@ FENNEC_JAVA_FILES = \
DoorHangerPopup.java \
EditBookmarkDialog.java \
favicons/Favicons.java \
favicons/LoadFaviconTask.java \
favicons/OnFaviconLoadedListener.java \
FilePickerResultHandler.java \
FilePickerResultHandlerSync.java \
FindInPageBar.java \

View File

@ -5,33 +5,16 @@
package org.mozilla.gecko.favicons;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.R;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.util.UiAsyncTask;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import android.content.ContentResolver;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.http.AndroidHttpClient;
import android.os.Handler;
import android.support.v4.util.LruCache;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
@ -49,10 +32,9 @@ public class Favicons {
private static int sFaviconSmallSize = -1;
private static int sFaviconLargeSize = -1;
private static Context sContext;
protected static Context sContext;
private static final Map<Long,LoadFaviconTask> sLoadTasks = Collections.synchronizedMap(new HashMap<Long, LoadFaviconTask>());
private static long sNextFaviconLoadId;
private static final LruCache<String, Bitmap> sFaviconCache = new LruCache<String, Bitmap>(1024 * 1024) {
@Override
protected int sizeOf(String url, Bitmap image) {
@ -67,22 +49,7 @@ public class Favicons {
// A cache holding the dominant colours of favicons - used by FaviconView to fill the extra space
// around a Favicon when it is asked to render a Favicon small than the view.
private static final LruCache<String, Integer> sColorCache = new LruCache<String, Integer>(256);
private static final String USER_AGENT = GeckoAppShell.getGeckoInterface().getDefaultUAString();
private static AndroidHttpClient sHttpClient;
public interface OnFaviconLoadedListener {
public void onFaviconLoaded(String url, Bitmap favicon);
}
private static synchronized AndroidHttpClient getHttpClient() {
if (sHttpClient != null)
return sHttpClient;
sHttpClient = AndroidHttpClient.newInstance(USER_AGENT);
return sHttpClient;
}
private static void dispatchResult(final String pageUrl, final Bitmap image,
static void dispatchResult(final String pageUrl, final Bitmap image,
final OnFaviconLoadedListener listener) {
if (pageUrl != null && image != null)
putFaviconInMemCache(pageUrl, image);
@ -125,7 +92,7 @@ public class Favicons {
LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener);
long taskId = task.getId();
long taskId = task.getmId();
sLoadTasks.put(taskId, task);
task.execute();
@ -195,8 +162,8 @@ public class Favicons {
cancelFaviconLoad(taskId);
}
}
if (sHttpClient != null)
sHttpClient.close();
LoadFaviconTask.closeHTTPClient();
}
public static boolean isLargeFavicon(Bitmap image) {
@ -234,172 +201,7 @@ public class Favicons {
sFaviconLargeSize = Math.round(sContext.getResources().getDimension(R.dimen.favicon_size_large));
}
}
private static class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
private long mId;
private String mPageUrl;
private String mFaviconUrl;
private OnFaviconLoadedListener mListener;
private int mFlags;
public LoadFaviconTask(Handler backgroundThreadHandler,
String pageUrl, String faviconUrl, int flags,
OnFaviconLoadedListener listener) {
super(backgroundThreadHandler);
synchronized(this) {
mId = ++sNextFaviconLoadId;
}
mPageUrl = pageUrl;
mFaviconUrl = faviconUrl;
mListener = listener;
mFlags = flags;
}
// Runs in background thread
private Bitmap loadFaviconFromDb() {
ContentResolver resolver = sContext.getContentResolver();
return BrowserDB.getFaviconForUrl(resolver, mPageUrl);
}
// Runs in background thread
private void saveFaviconToDb(final Bitmap favicon) {
if ((mFlags & FLAG_PERSIST) == 0) {
return;
}
ContentResolver resolver = sContext.getContentResolver();
BrowserDB.updateFaviconForUrl(resolver, mPageUrl, favicon, mFaviconUrl);
}
// Runs in background thread
private Bitmap downloadFavicon(URL faviconUrl) {
if (mFaviconUrl.startsWith("jar:jar:")) {
return GeckoJarReader.getBitmap(sContext.getResources(), mFaviconUrl);
}
URI uri;
try {
uri = faviconUrl.toURI();
} catch (URISyntaxException e) {
Log.e(LOGTAG, "URISyntaxException getting URI for favicon", e);
return null;
}
// only get favicons for HTTP/HTTPS
String scheme = uri.getScheme();
if (!"http".equals(scheme) && !"https".equals(scheme))
return null;
// skia decoder sometimes returns null; workaround is to use BufferedHttpEntity
// http://groups.google.com/group/android-developers/browse_thread/thread/171b8bf35dbbed96/c3ec5f45436ceec8?lnk=raot
Bitmap image = null;
try {
HttpGet request = new HttpGet(faviconUrl.toURI());
HttpResponse response = getHttpClient().execute(request);
if (response == null)
return null;
if (response.getStatusLine() != null) {
// Was the response a failure?
int status = response.getStatusLine().getStatusCode();
if (status >= 400) {
putFaviconInFailedCache(mPageUrl, FAILED_EXPIRY_NEVER);
return null;
}
}
HttpEntity entity = response.getEntity();
if (entity == null)
return null;
if (entity.getContentType() != null) {
// Is the content type valid? Might be a captive portal.
String contentType = entity.getContentType().getValue();
if (!contentType.contains("image"))
return null;
}
BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
InputStream contentStream = bufferedEntity.getContent();
image = BitmapUtils.decodeStream(contentStream);
contentStream.close();
} catch (IOException e) {
Log.e(LOGTAG, "IOException reading favicon:", e);
} catch (URISyntaxException e) {
Log.e(LOGTAG, "URISyntaxException reading favicon:", e);
}
return image;
}
@Override
protected Bitmap doInBackground(Void... unused) {
Bitmap image = null;
if (isCancelled())
return null;
URL faviconUrl = null;
// Handle the case of malformed favicon URL
try {
// If favicon is empty, fallback to default favicon URI
if (mFaviconUrl == null || mFaviconUrl.isEmpty()) {
// Handle the case of malformed URL
URL pageUrl = null;
pageUrl = new URL(mPageUrl);
faviconUrl = new URL(pageUrl.getProtocol(), pageUrl.getAuthority(), "/favicon.ico");
mFaviconUrl = faviconUrl.toString();
} else {
faviconUrl = new URL(mFaviconUrl);
}
} catch (MalformedURLException e) {
Log.d(LOGTAG, "The provided favicon URL is not valid");
return null;
}
if (isCancelled())
return null;
String storedFaviconUrl = getFaviconUrlForPageUrl(mPageUrl);
if (storedFaviconUrl != null && storedFaviconUrl.equals(mFaviconUrl)) {
image = loadFaviconFromDb();
if (image != null && image.getWidth() > 0 && image.getHeight() > 0)
return ((mFlags & FLAG_SCALE) != 0) ? scaleImage(image) : image;
}
if (isCancelled())
return null;
image = downloadFavicon(faviconUrl);
if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
saveFaviconToDb(image);
image = ((mFlags & FLAG_SCALE) != 0) ? scaleImage(image) : image;
} else {
image = null;
}
return image;
}
@Override
protected void onPostExecute(final Bitmap image) {
sLoadTasks.remove(mId);
dispatchResult(mPageUrl, image, mListener);
}
@Override
protected void onCancelled() {
sLoadTasks.remove(mId);
// Note that we don't call the listener callback if the
// favicon load is cancelled.
}
public long getId() {
return mId;
}
public static void removeLoadTask(long taskId) {
sLoadTasks.remove(taskId);
}
}

View File

@ -0,0 +1,213 @@
/* 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.ContentResolver;
import android.graphics.Bitmap;
import android.net.http.AndroidHttpClient;
import android.os.Handler;
import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.BufferedHttpEntity;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.util.GeckoJarReader;
import org.mozilla.gecko.util.UiAsyncTask;
import static org.mozilla.gecko.favicons.Favicons.sContext;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
/**
* Class representing the asynchronous task to load a Favicon which is not currently in the in-memory
* cache.
* The implementation initially tries to get the Favicon from the database. Upon failure, the icon
* is loaded from the internet.
*/
public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
private static final String LOGTAG = "LoadFaviconTask";
public static final int FLAG_PERSIST = 1;
public static final int FLAG_SCALE = 2;
private long mNextFaviconLoadId;
private long mId;
private String mPageUrl;
private String mFaviconUrl;
private OnFaviconLoadedListener mListener;
private int mFlags;
static AndroidHttpClient sHttpClient = AndroidHttpClient.newInstance(GeckoAppShell.getGeckoInterface().getDefaultUAString());
public LoadFaviconTask(Handler backgroundThreadHandler,
String aPageUrl, String aFaviconUrl, int aFlags,
OnFaviconLoadedListener aListener) {
super(backgroundThreadHandler);
synchronized(this) {
mId = ++mNextFaviconLoadId;
}
mPageUrl = aPageUrl;
mFaviconUrl = aFaviconUrl;
mListener = aListener;
mFlags = aFlags;
}
// Runs in background thread
private Bitmap loadFaviconFromDb() {
ContentResolver resolver = sContext.getContentResolver();
return BrowserDB.getFaviconForUrl(resolver, mPageUrl);
}
// Runs in background thread
private void saveFaviconToDb(final Bitmap favicon) {
if ((mFlags & FLAG_PERSIST) == 0) {
return;
}
ContentResolver resolver = sContext.getContentResolver();
BrowserDB.updateFaviconForUrl(resolver, mPageUrl, favicon, mFaviconUrl);
}
// Runs in background thread
private Bitmap downloadFavicon(URL targetFaviconURL) {
if (mFaviconUrl.startsWith("jar:jar:")) {
return GeckoJarReader.getBitmap(sContext.getResources(), mFaviconUrl);
}
URI uri;
try {
uri = targetFaviconURL.toURI();
} catch (URISyntaxException e) {
Log.d(LOGTAG, "Could not get URI for favicon");
return null;
}
// only get favicons for HTTP/HTTPS
String scheme = uri.getScheme();
if (!"http".equals(scheme) && !"https".equals(scheme))
return null;
// skia decoder sometimes returns null; workaround is to use BufferedHttpEntity
// http://groups.google.com/group/android-developers/browse_thread/thread/171b8bf35dbbed96/c3ec5f45436ceec8?lnk=raot
Bitmap image = null;
try {
HttpGet request = new HttpGet(targetFaviconURL.toURI());
HttpResponse response = sHttpClient.execute(request);
if (response == null)
return null;
if (response.getStatusLine() != null) {
// Was the response a failure?
int status = response.getStatusLine().getStatusCode();
if (status >= 400) {
Favicons.putFaviconInFailedCache(mPageUrl, Favicons.FAILED_EXPIRY_NEVER);
return null;
}
}
HttpEntity entity = response.getEntity();
if (entity == null)
return null;
if (entity.getContentType() != null) {
// Is the content type valid? Might be a captive portal.
String contentType = entity.getContentType().getValue();
if (contentType.indexOf("image") == -1)
return null;
}
BufferedHttpEntity bufferedEntity = new BufferedHttpEntity(entity);
InputStream contentStream = bufferedEntity.getContent();
image = BitmapUtils.decodeStream(contentStream);
contentStream.close();
} catch (Exception e) {
Log.e(LOGTAG, "Error reading favicon", e);
}
return image;
}
@Override
protected Bitmap doInBackground(Void... unused) {
Bitmap image;
if (isCancelled())
return null;
URL faviconURLToDownload;
// Handle the case of malformed favicon URL
try {
// If favicon is empty, fallback to default favicon URI
if (mFaviconUrl == null || mFaviconUrl.length() == 0) {
// Handle the case of malformed URL
URL targetPageURL = new URL(mPageUrl);
faviconURLToDownload = new URL(targetPageURL.getProtocol(), targetPageURL.getAuthority(), "/favicon.ico");
mFaviconUrl = faviconURLToDownload.toString();
} else {
faviconURLToDownload = new URL(mFaviconUrl);
}
} catch (MalformedURLException e) {
Log.d(LOGTAG, "The provided favicon URL is not valid");
return null;
}
if (isCancelled())
return null;
String storedFaviconUrl = Favicons.getFaviconUrlForPageUrl(mPageUrl);
if (storedFaviconUrl != null && storedFaviconUrl.equals(mFaviconUrl)) {
image = loadFaviconFromDb();
if (image != null && image.getWidth() > 0 && image.getHeight() > 0)
return ((mFlags & FLAG_SCALE) != 0) ? Favicons.scaleImage(image) : image;
}
if (isCancelled())
return null;
image = downloadFavicon(faviconURLToDownload);
if (image != null && image.getWidth() > 0 && image.getHeight() > 0) {
saveFaviconToDb(image);
image = ((mFlags & FLAG_SCALE) != 0) ? Favicons.scaleImage(image) : image;
} else {
image = null;
}
return image;
}
@Override
protected void onPostExecute(final Bitmap image) {
Favicons.removeLoadTask(mId);
Favicons.dispatchResult(mPageUrl, image, mListener);
}
@Override
protected void onCancelled() {
Favicons.removeLoadTask(mId);
// Note that we don't call the listener callback if the
// favicon load is cancelled.
}
long getId() {
return mId;
}
static void closeHTTPClient() {
if (sHttpClient != null) {
sHttpClient.close();
}
}
}

View File

@ -0,0 +1,14 @@
/* 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;
/**
* Interface to be implemented by objects wishing to listen for favicon load completion events.
*/
public interface OnFaviconLoadedListener {
void onFaviconLoaded(String url, Bitmap favicon);
}

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.home;
import org.mozilla.gecko.EditBookmarkDialog;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoProfile;
@ -236,7 +237,7 @@ abstract class HomeFragment extends Fragment {
@Override
public void onPostExecute(String faviconUrl) {
Favicons.OnFaviconLoadedListener listener = new Favicons.OnFaviconLoadedListener() {
OnFaviconLoadedListener listener = new OnFaviconLoadedListener() {
@Override
public void onFaviconLoaded(String url, Bitmap favicon) {
GeckoAppShell.createShortcut(mTitle, mUrl, favicon, "");