Browse Source

Getting rid of google favicon fetching and using generated favicon as fallbacks

master
anthony restaino 7 years ago
parent
commit
1a1efc9da6
  1. 48
      app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java
  2. 57
      app/src/main/java/acr/browser/lightning/favicon/FaviconModel.java
  3. 4
      app/src/main/java/acr/browser/lightning/favicon/FaviconUtils.java
  4. 110
      app/src/main/java/acr/browser/lightning/favicon/ImageFetcher.java
  5. 2
      app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java

48
app/src/main/java/acr/browser/lightning/constant/BookmarkPage.java

@ -6,6 +6,7 @@ package acr.browser.lightning.constant;
import android.app.Activity; import android.app.Activity;
import android.app.Application; import android.app.Application;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
@ -28,6 +29,8 @@ import acr.browser.lightning.R;
import acr.browser.lightning.app.BrowserApp; import acr.browser.lightning.app.BrowserApp;
import acr.browser.lightning.database.HistoryItem; import acr.browser.lightning.database.HistoryItem;
import acr.browser.lightning.database.bookmark.BookmarkModel; import acr.browser.lightning.database.bookmark.BookmarkModel;
import acr.browser.lightning.favicon.FaviconModel;
import acr.browser.lightning.favicon.FaviconUtils;
import acr.browser.lightning.utils.Preconditions; import acr.browser.lightning.utils.Preconditions;
import acr.browser.lightning.utils.ThemeUtils; import acr.browser.lightning.utils.ThemeUtils;
import acr.browser.lightning.utils.Utils; import acr.browser.lightning.utils.Utils;
@ -75,6 +78,7 @@ public final class BookmarkPage {
private static final String END = "</div></body></html>"; private static final String END = "</div></body></html>";
private static final String FOLDER_ICON = "folder.png"; private static final String FOLDER_ICON = "folder.png";
private static final String DEFAULT_ICON = "default.png";
@NonNull @NonNull
private static File getBookmarkPage(@NonNull Application application, @Nullable String folder) { private static File getBookmarkPage(@NonNull Application application, @Nullable String folder) {
@ -87,8 +91,14 @@ public final class BookmarkPage {
return new File(application.getCacheDir(), FOLDER_ICON); return new File(application.getCacheDir(), FOLDER_ICON);
} }
@NonNull
private static File getDefaultIconFile(@NonNull Application application) {
return new File(application.getCacheDir(), DEFAULT_ICON);
}
@Inject Application mApp; @Inject Application mApp;
@Inject BookmarkModel mManager; @Inject BookmarkModel mBookmarkModel;
@Inject FaviconModel mFaviconModel;
@NonNull private final Bitmap mFolderIcon; @NonNull private final Bitmap mFolderIcon;
@NonNull private final String mTitle; @NonNull private final String mTitle;
@ -104,7 +114,8 @@ public final class BookmarkPage {
return Single.create(new SingleAction<String>() { return Single.create(new SingleAction<String>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<String> subscriber) {
cacheDefaultFolderIcon(); cacheIcon(mFolderIcon, getFaviconFile(mApp));
cacheIcon(mFaviconModel.getDefaultBitmapForString(null), getDefaultIconFile(mApp));
buildBookmarkPage(null); buildBookmarkPage(null);
File bookmarkWebPage = getBookmarkPage(mApp, null); File bookmarkWebPage = getBookmarkPage(mApp, null);
@ -115,12 +126,12 @@ public final class BookmarkPage {
}); });
} }
private void cacheDefaultFolderIcon() { private void cacheIcon(@NonNull Bitmap icon, @NonNull File file) {
FileOutputStream outputStream = null; FileOutputStream outputStream = null;
try { try {
outputStream = new FileOutputStream(getFaviconFile(mApp)); outputStream = new FileOutputStream(file);
mFolderIcon.compress(Bitmap.CompressFormat.PNG, 100, outputStream); icon.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
mFolderIcon.recycle(); icon.recycle();
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
@ -129,14 +140,14 @@ public final class BookmarkPage {
} }
private void buildBookmarkPage(@Nullable final String folder) { private void buildBookmarkPage(@Nullable final String folder) {
mManager.getBookmarksFromFolderSorted(folder) mBookmarkModel.getBookmarksFromFolderSorted(folder)
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { .subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override @Override
public void onItem(@Nullable final List<HistoryItem> list) { public void onItem(@Nullable final List<HistoryItem> list) {
Preconditions.checkNonNull(list); Preconditions.checkNonNull(list);
if (folder == null) { if (folder == null) {
mManager.getFoldersSorted() mBookmarkModel.getFoldersSorted()
.subscribe(new SingleOnSubscribe<List<HistoryItem>>() { .subscribe(new SingleOnSubscribe<List<HistoryItem>>() {
@Override @Override
public void onItem(@Nullable List<HistoryItem> item) { public void onItem(@Nullable List<HistoryItem> item) {
@ -171,9 +182,26 @@ public final class BookmarkPage {
bookmarkBuilder.append(folderIconPath); bookmarkBuilder.append(folderIconPath);
buildBookmarkPage(item.getTitle()); buildBookmarkPage(item.getTitle());
} else { } else {
Uri bookmarkUri = FaviconUtils.safeUri(item.getUrl());
String faviconFileUrl;
if (bookmarkUri != null) {
File faviconFile = FaviconModel.getFaviconCacheFile(mApp, bookmarkUri);
if (!faviconFile.exists()) {
mFaviconModel.cacheFaviconForUrl(mFaviconModel.getDefaultBitmapForString(item.getTitle()), item.getUrl())
.subscribe();
}
faviconFileUrl = Constants.FILE + faviconFile;
} else {
faviconFileUrl = Constants.FILE + getDefaultIconFile(mApp);
}
bookmarkBuilder.append(item.getUrl()); bookmarkBuilder.append(item.getUrl());
bookmarkBuilder.append(PART2).append(PART3); bookmarkBuilder.append(PART2).append(faviconFileUrl);
bookmarkBuilder.append(item.getUrl());
} }
bookmarkBuilder.append(PART4); bookmarkBuilder.append(PART4);
bookmarkBuilder.append(item.getTitle()); bookmarkBuilder.append(item.getTitle());

57
app/src/main/java/acr/browser/lightning/favicon/FaviconModel.java

@ -2,10 +2,13 @@ package acr.browser.lightning.favicon;
import android.app.Application; import android.app.Application;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.ColorInt; import android.support.annotation.ColorInt;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
@ -38,7 +41,7 @@ public class FaviconModel {
private static final String TAG = "FaviconModel"; private static final String TAG = "FaviconModel";
@NonNull private final ImageFetcher mImageFetcher; @NonNull private final BitmapFactory.Options mLoaderOptions = new BitmapFactory.Options();
@NonNull private final Application mApplication; @NonNull private final Application mApplication;
@NonNull private final LruCache<String, Bitmap> mFaviconCache = new LruCache<String, Bitmap>((int) FileUtils.megabytesToBytes(1)) { @NonNull private final LruCache<String, Bitmap> mFaviconCache = new LruCache<String, Bitmap>((int) FileUtils.megabytesToBytes(1)) {
@Override @Override
@ -51,7 +54,6 @@ public class FaviconModel {
@Inject @Inject
FaviconModel(@NonNull Application application) { FaviconModel(@NonNull Application application) {
mImageFetcher = new ImageFetcher();
mApplication = application; mApplication = application;
mBookmarkIconSize = mApplication.getResources().getDimensionPixelSize(R.dimen.bookmark_item_icon_size); mBookmarkIconSize = mApplication.getResources().getDimensionPixelSize(R.dimen.bookmark_item_icon_size);
} }
@ -75,10 +77,12 @@ public class FaviconModel {
} }
@NonNull @NonNull
private Bitmap getDefaultBitmapForCharacter(@NonNull Character character) { public Bitmap getDefaultBitmapForString(@Nullable String title) {
@ColorInt int defaultFaviconColor = DrawableUtils.characterToColorHash(character, mApplication); Character firstTitleCharacter = !TextUtils.isEmpty(title) ? title.charAt(0) : '?';
return DrawableUtils.getRoundedLetterImage(character, @ColorInt int defaultFaviconColor = DrawableUtils.characterToColorHash(firstTitleCharacter, mApplication);
return DrawableUtils.getRoundedLetterImage(firstTitleCharacter,
mBookmarkIconSize, mBookmarkIconSize,
mBookmarkIconSize, mBookmarkIconSize,
defaultFaviconColor); defaultFaviconColor);
@ -109,8 +113,9 @@ public class FaviconModel {
* @param uri the URI to use as a unique identifier. * @param uri the URI to use as a unique identifier.
* @return a valid cache file. * @return a valid cache file.
*/ */
@WorkerThread
@NonNull @NonNull
private static File createFaviconCacheFile(@NonNull Application app, @NonNull Uri uri) { public static File getFaviconCacheFile(@NonNull Application app, @NonNull Uri uri) {
FaviconUtils.assertUriSafe(uri); FaviconUtils.assertUriSafe(uri);
String hash = String.valueOf(uri.getHost().hashCode()); String hash = String.valueOf(uri.getHost().hashCode());
@ -122,16 +127,13 @@ public class FaviconModel {
* Retrieves the favicon for a URL, * Retrieves the favicon for a URL,
* may be from network or cache. * may be from network or cache.
* *
* @param url The URL that we should retrieve the * @param url The URL that we should retrieve the
* favicon for. * favicon for.
* @param title The title for the web page. * @param title The title for the web page.
* @param allowGoogleService True to allow grabbing favicons
* from Google, false otherwise. @return an observable that emits a bitmap if one is found,
*/ */
@NonNull @NonNull
public Single<Bitmap> faviconForUrl(@NonNull final String url, public Single<Bitmap> faviconForUrl(@NonNull final String url,
@NonNull final String title, @NonNull final String title) {
final boolean allowGoogleService) {
return Single.create(new SingleAction<Bitmap>() { return Single.create(new SingleAction<Bitmap>() {
@Override @Override
public void onSubscribe(@NonNull SingleSubscriber<Bitmap> subscriber) { public void onSubscribe(@NonNull SingleSubscriber<Bitmap> subscriber) {
@ -139,7 +141,7 @@ public class FaviconModel {
if (uri == null) { if (uri == null) {
Bitmap newFavicon = Utils.padFavicon(getDefaultBitmapForCharacter('?')); Bitmap newFavicon = Utils.padFavicon(getDefaultBitmapForString(title));
subscriber.onItem(newFavicon); subscriber.onItem(newFavicon);
subscriber.onComplete(); subscriber.onComplete();
@ -147,25 +149,19 @@ public class FaviconModel {
return; return;
} }
Character firstTitleCharacter = !title.isEmpty() ? title.charAt(0) : '?'; File faviconCacheFile = getFaviconCacheFile(mApplication, uri);
File faviconCacheFile = createFaviconCacheFile(mApplication, uri);
Bitmap favicon = getFaviconFromMemCache(url); Bitmap favicon = getFaviconFromMemCache(url);
if (faviconCacheFile.exists() && favicon == null) { if (faviconCacheFile.exists() && favicon == null) {
favicon = mImageFetcher.retrieveFaviconFromCache(faviconCacheFile); favicon = BitmapFactory.decodeFile(faviconCacheFile.getPath(), mLoaderOptions);
if (favicon != null) { if (favicon != null) {
addFaviconToMemCache(url, favicon); addFaviconToMemCache(url, favicon);
} }
} }
if (favicon == null) { if (favicon != null) {
// TODO: 6/5/17 figure out if optimistic favicon retrieval should be added back or dropped
// favicon = mImageFetcher.retrieveBitmapFromDomain(uri);
} else {
Bitmap newFavicon = Utils.padFavicon(favicon); Bitmap newFavicon = Utils.padFavicon(favicon);
subscriber.onItem(newFavicon); subscriber.onItem(newFavicon);
@ -174,18 +170,7 @@ public class FaviconModel {
return; return;
} }
// if (favicon == null && allowGoogleService) { favicon = getDefaultBitmapForString(title);
// favicon = mImageFetcher.retrieveBitmapFromGoogle(uri);
// }
// if (favicon != null) {
// addFaviconToMemCache(url, favicon);
// cacheFaviconForUrl(favicon, url).subscribe();
// }
// if (favicon == null) {
favicon = getDefaultBitmapForCharacter(firstTitleCharacter);
// }
Bitmap newFavicon = Utils.padFavicon(favicon); Bitmap newFavicon = Utils.padFavicon(favicon);
@ -220,7 +205,7 @@ public class FaviconModel {
FileOutputStream fos = null; FileOutputStream fos = null;
try { try {
File image = createFaviconCacheFile(mApplication, uri); File image = getFaviconCacheFile(mApplication, uri);
fos = new FileOutputStream(image); fos = new FileOutputStream(image);
favicon.compress(Bitmap.CompressFormat.PNG, 100, fos); favicon.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush(); fos.flush();

4
app/src/main/java/acr/browser/lightning/favicon/FaviconUtils.java

@ -8,9 +8,9 @@ import android.text.TextUtils;
/** /**
* Simple utils for favicon fetching. * Simple utils for favicon fetching.
*/ */
class FaviconUtils { public class FaviconUtils {
@Nullable @Nullable
static Uri safeUri(@NonNull String url) { public static Uri safeUri(@NonNull String url) {
if (TextUtils.isEmpty(url)) { if (TextUtils.isEmpty(url)) {
return null; return null;
} }

110
app/src/main/java/acr/browser/lightning/favicon/ImageFetcher.java

@ -1,110 +0,0 @@
package acr.browser.lightning.favicon;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import acr.browser.lightning.utils.Utils;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
/**
* An image fetcher that creates image
* loading requests on demand.
*/
class ImageFetcher {
private static final String TAG = "ImageFetcher";
@NonNull private final BitmapFactory.Options mLoaderOptions = new BitmapFactory.Options();
@NonNull private final OkHttpClient mHttpClient = new OkHttpClient();
ImageFetcher() {}
@Nullable
Bitmap retrieveFaviconFromCache(@NonNull File cacheFile) {
return BitmapFactory.decodeFile(cacheFile.getPath(), mLoaderOptions);
}
@Nullable
Bitmap retrieveBitmapFromDomain(@NonNull Uri uri) {
FaviconUtils.assertUriSafe(uri);
String faviconUrlGuess = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico";
return retrieveBitmapFromUrl(faviconUrlGuess);
}
@Nullable
Bitmap retrieveBitmapFromGoogle(@NonNull Uri uri) {
FaviconUtils.assertUriSafe(uri);
String googleFaviconUrl = "https://www.google.com/s2/favicons?domain_url=" + uri.getHost();
return retrieveBitmapFromUrl(googleFaviconUrl);
}
@Nullable
private Bitmap retrieveBitmapFromUrl(@NonNull String url) {
Bitmap icon = null;
InputStream boundsStream = null;
InputStream iconStream = null;
try {
mLoaderOptions.inSampleSize = 1;
mLoaderOptions.inJustDecodeBounds = true;
Request imageRequest = new Request.Builder().url(url).build();
Response boundsResponse = mHttpClient.newCall(imageRequest).execute();
ResponseBody boundsBody = boundsResponse.body();
if (boundsBody == null) {
return null;
}
boundsStream = boundsBody.byteStream();
BitmapFactory.decodeStream(boundsStream, null, mLoaderOptions);
boundsBody.close();
int size = Utils.dpToPx(24);
mLoaderOptions.inSampleSize = Utils.calculateInSampleSize(mLoaderOptions, size, size);
mLoaderOptions.inJustDecodeBounds = false;
Response imageResponse = mHttpClient.newCall(imageRequest).execute();
ResponseBody imageBody = imageResponse.body();
if (imageBody == null) {
return null;
}
iconStream = imageBody.byteStream();
icon = BitmapFactory.decodeStream(iconStream, null, mLoaderOptions);
imageBody.close();
} catch (IOException exception) {
Log.d(TAG, "Unable to download icon: " + url);
} finally {
Utils.close(boundsStream);
Utils.close(iconStream);
}
return icon;
}
}

2
app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java

@ -537,7 +537,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
Subscription oldSubscription = mFaviconFetchSubscriptions.get(url); Subscription oldSubscription = mFaviconFetchSubscriptions.get(url);
SubscriptionUtils.safeUnsubscribe(oldSubscription); SubscriptionUtils.safeUnsubscribe(oldSubscription);
final Subscription faviconSubscription = mFaviconModel.faviconForUrl(url, web.getTitle(), true) final Subscription faviconSubscription = mFaviconModel.faviconForUrl(url, web.getTitle())
.subscribeOn(Schedulers.worker()) .subscribeOn(Schedulers.worker())
.observeOn(Schedulers.main()) .observeOn(Schedulers.main())
.subscribe(new SingleOnSubscribe<Bitmap>() { .subscribe(new SingleOnSubscribe<Bitmap>() {

Loading…
Cancel
Save