anthony restaino
8 years ago
9 changed files with 314 additions and 224 deletions
@ -0,0 +1,139 @@
@@ -0,0 +1,139 @@
|
||||
package acr.browser.lightning.favicon; |
||||
|
||||
import android.app.Application; |
||||
import android.graphics.Bitmap; |
||||
import android.net.Uri; |
||||
import android.support.annotation.NonNull; |
||||
import android.util.Log; |
||||
|
||||
import com.anthonycr.bonsai.Completable; |
||||
import com.anthonycr.bonsai.CompletableAction; |
||||
import com.anthonycr.bonsai.CompletableSubscriber; |
||||
import com.anthonycr.bonsai.Single; |
||||
import com.anthonycr.bonsai.SingleAction; |
||||
import com.anthonycr.bonsai.SingleSubscriber; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
|
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* Reactive model that can fetch favicons |
||||
* from URLs and also cache them. |
||||
*/ |
||||
public class FaviconModel { |
||||
|
||||
private static final String TAG = "FaviconModel"; |
||||
|
||||
private final ImageFetcher mImageFetcher; |
||||
|
||||
public FaviconModel() { |
||||
mImageFetcher = new ImageFetcher(); |
||||
} |
||||
|
||||
@NonNull |
||||
private static File createFaviconCacheFile(@NonNull Application app, @NonNull Uri uri) { |
||||
FaviconUtils.assertUriSafe(uri); |
||||
|
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
|
||||
return new File(app.getCacheDir(), hash + ".png"); |
||||
} |
||||
|
||||
|
||||
@NonNull |
||||
public Single<Bitmap> faviconForUrl(@NonNull final String url, |
||||
@NonNull final Bitmap defaultFavicon, |
||||
final boolean allowGoogleService) { |
||||
return Single.create(new SingleAction<Bitmap>() { |
||||
@Override |
||||
public void onSubscribe(@NonNull SingleSubscriber<Bitmap> subscriber) { |
||||
Uri uri = FaviconUtils.safeUri(url); |
||||
|
||||
if (uri == null) { |
||||
|
||||
Bitmap newFavicon = Utils.padFavicon(defaultFavicon); |
||||
|
||||
subscriber.onItem(newFavicon); |
||||
subscriber.onComplete(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
Application app = BrowserApp.getApplication(); |
||||
|
||||
File faviconCacheFile = createFaviconCacheFile(app, uri); |
||||
|
||||
Bitmap favicon = null; |
||||
|
||||
if (faviconCacheFile.exists()) { |
||||
favicon = mImageFetcher.retrieveFaviconFromCache(faviconCacheFile); |
||||
} |
||||
|
||||
if (favicon == null) { |
||||
favicon = mImageFetcher.retrieveBitmapFromDomain(uri); |
||||
} else { |
||||
Bitmap newFavicon = Utils.padFavicon(favicon); |
||||
|
||||
subscriber.onItem(newFavicon); |
||||
subscriber.onComplete(); |
||||
|
||||
return; |
||||
} |
||||
|
||||
if (favicon == null && allowGoogleService) { |
||||
favicon = mImageFetcher.retrieveBitmapFromGoogle(uri); |
||||
} |
||||
|
||||
if (favicon != null) { |
||||
cacheFaviconForUrl(favicon, url).subscribe(); |
||||
} |
||||
|
||||
if (favicon == null) { |
||||
favicon = defaultFavicon; |
||||
} |
||||
|
||||
Bitmap newFavicon = Utils.padFavicon(favicon); |
||||
|
||||
subscriber.onItem(newFavicon); |
||||
subscriber.onComplete(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@NonNull |
||||
public Completable cacheFaviconForUrl(@NonNull final Bitmap favicon, |
||||
@NonNull final String url) { |
||||
return Completable.create(new CompletableAction() { |
||||
@Override |
||||
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
||||
Uri uri = FaviconUtils.safeUri(url); |
||||
|
||||
if (uri == null) { |
||||
subscriber.onComplete(); |
||||
return; |
||||
} |
||||
|
||||
Application app = BrowserApp.getApplication(); |
||||
|
||||
Log.d(TAG, "Caching icon for " + uri.getHost()); |
||||
FileOutputStream fos = null; |
||||
|
||||
try { |
||||
File image = createFaviconCacheFile(app, uri); |
||||
fos = new FileOutputStream(image); |
||||
favicon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
Log.e(TAG, "Unable to cache favicon", e); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
package acr.browser.lightning.favicon; |
||||
|
||||
import android.net.Uri; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.text.TextUtils; |
||||
|
||||
/** |
||||
* Created by anthonycr on 4/13/17. |
||||
*/ |
||||
|
||||
class FaviconUtils { |
||||
@Nullable |
||||
static Uri safeUri(@NonNull String url) { |
||||
if (TextUtils.isEmpty(url)) { |
||||
return null; |
||||
} |
||||
|
||||
Uri uri = Uri.parse(url); |
||||
|
||||
if (uri.getHost() == null || uri.getScheme() == null) { |
||||
return null; |
||||
} |
||||
|
||||
return uri; |
||||
} |
||||
|
||||
static void assertUriSafe(@Nullable Uri uri) { |
||||
if (uri == null || TextUtils.isEmpty(uri.getScheme()) || TextUtils.isEmpty(uri.getHost())) { |
||||
throw new RuntimeException("Unsafe uri provided"); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,81 @@
@@ -0,0 +1,81 @@
|
||||
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.InputStream; |
||||
import java.net.HttpURLConnection; |
||||
import java.net.URL; |
||||
|
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* 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(); |
||||
|
||||
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.toString(); |
||||
|
||||
return retrieveBitmapFromUrl(googleFaviconUrl); |
||||
} |
||||
|
||||
|
||||
@Nullable |
||||
private Bitmap retrieveBitmapFromUrl(@NonNull String url) { |
||||
InputStream in = null; |
||||
Bitmap icon = null; |
||||
|
||||
try { |
||||
final URL urlDownload = new URL(url); |
||||
final HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection(); |
||||
connection.setDoInput(true); |
||||
connection.setConnectTimeout(1000); |
||||
connection.setReadTimeout(1000); |
||||
connection.connect(); |
||||
in = connection.getInputStream(); |
||||
|
||||
if (in != null) { |
||||
icon = BitmapFactory.decodeStream(in, null, mLoaderOptions); |
||||
} |
||||
} catch (Exception ignored) { |
||||
Log.d(TAG, "Could not download icon from: " + url); |
||||
} finally { |
||||
Utils.close(in); |
||||
} |
||||
|
||||
return icon; |
||||
} |
||||
|
||||
|
||||
} |
@ -1,42 +0,0 @@
@@ -1,42 +0,0 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.app.Application; |
||||
import android.graphics.Bitmap; |
||||
import android.net.Uri; |
||||
import android.util.Log; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
|
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
class IconCacheTask implements Runnable { |
||||
private final Uri uri; |
||||
private final Bitmap icon; |
||||
private final Application app; |
||||
|
||||
public IconCacheTask(final Uri uri, final Bitmap icon, final Application app) { |
||||
this.uri = uri; |
||||
this.icon = icon; |
||||
this.app = app; |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File image = new File(app.getCacheDir(), hash + ".png"); |
||||
fos = new FileOutputStream(image); |
||||
icon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
} |
@ -1,152 +0,0 @@
@@ -1,152 +0,0 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.app.Application; |
||||
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 com.anthonycr.bonsai.Single; |
||||
import com.anthonycr.bonsai.SingleAction; |
||||
import com.anthonycr.bonsai.SingleSubscriber; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.net.HttpURLConnection; |
||||
import java.net.URL; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* An ImageDownloader that creates image |
||||
* loading requests on demand. |
||||
*/ |
||||
public class ImageDownloader { |
||||
|
||||
private static final String TAG = "ImageDownloader"; |
||||
|
||||
@Inject Application mApp; |
||||
|
||||
@NonNull private final Bitmap mDefaultBitmap; |
||||
@NonNull private final BitmapFactory.Options mLoaderOptions = new BitmapFactory.Options(); |
||||
|
||||
public ImageDownloader(@NonNull Bitmap defaultBitmap) { |
||||
BrowserApp.getAppComponent().inject(this); |
||||
mDefaultBitmap = defaultBitmap; |
||||
} |
||||
|
||||
/** |
||||
* Creates a new image request for the given url. |
||||
* Emits the bitmap associated with that url, or |
||||
* the default bitmap if none was found. |
||||
* |
||||
* @param url the url for which to retrieve the bitmap. |
||||
* @return a single that emits the bitmap that was found. |
||||
*/ |
||||
@NonNull |
||||
public Single<Bitmap> newImageRequest(@Nullable final String url) { |
||||
return Single.create(new SingleAction<Bitmap>() { |
||||
@Override |
||||
public void onSubscribe(@NonNull SingleSubscriber<Bitmap> subscriber) { |
||||
Bitmap favicon = retrieveFaviconForUrl(url); |
||||
|
||||
Bitmap paddedFavicon = Utils.padFavicon(favicon); |
||||
|
||||
subscriber.onItem(paddedFavicon); |
||||
subscriber.onComplete(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
@NonNull |
||||
private Bitmap retrieveFaviconForUrl(@Nullable String url) { |
||||
|
||||
// unique path for each url that is bookmarked.
|
||||
if (url == null) { |
||||
return mDefaultBitmap; |
||||
} |
||||
|
||||
Bitmap icon; |
||||
File cache = mApp.getCacheDir(); |
||||
Uri uri = Uri.parse(url); |
||||
|
||||
if (uri.getHost() == null || uri.getScheme() == null || Constants.FILE.startsWith(uri.getScheme())) { |
||||
return mDefaultBitmap; |
||||
} |
||||
|
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
File image = new File(cache, hash + ".png"); |
||||
String urlDisplay = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico"; |
||||
|
||||
if (image.exists()) { |
||||
// If image exists, pull it from the cache
|
||||
icon = BitmapFactory.decodeFile(image.getPath()); |
||||
} else { |
||||
// Otherwise, load it from network
|
||||
icon = retrieveBitmapFromUrl(urlDisplay); |
||||
} |
||||
|
||||
if (icon == null) { |
||||
String googleFaviconUrl = "https://www.google.com/s2/favicons?domain_url=" + uri.toString(); |
||||
icon = retrieveBitmapFromUrl(googleFaviconUrl); |
||||
} |
||||
|
||||
if (icon == null) { |
||||
return mDefaultBitmap; |
||||
} else { |
||||
cacheBitmap(image, icon); |
||||
|
||||
return icon; |
||||
} |
||||
} |
||||
|
||||
private void cacheBitmap(@NonNull File cacheFile, @NonNull Bitmap imageToCache) { |
||||
FileOutputStream fos = null; |
||||
|
||||
try { |
||||
fos = new FileOutputStream(cacheFile); |
||||
imageToCache.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
Log.e(TAG, "Could not cache icon"); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
|
||||
@Nullable |
||||
private Bitmap retrieveBitmapFromUrl(@NonNull String url) { |
||||
InputStream in = null; |
||||
Bitmap icon = null; |
||||
|
||||
try { |
||||
final URL urlDownload = new URL(url); |
||||
final HttpURLConnection connection = (HttpURLConnection) urlDownload.openConnection(); |
||||
connection.setDoInput(true); |
||||
connection.setConnectTimeout(1000); |
||||
connection.setReadTimeout(1000); |
||||
connection.connect(); |
||||
in = connection.getInputStream(); |
||||
|
||||
if (in != null) { |
||||
icon = BitmapFactory.decodeStream(in, null, mLoaderOptions); |
||||
} |
||||
} catch (Exception ignored) { |
||||
Log.d(TAG, "Could not download icon from: " + url); |
||||
} finally { |
||||
Utils.close(in); |
||||
} |
||||
|
||||
return icon; |
||||
} |
||||
|
||||
|
||||
} |
Loading…
Reference in new issue