You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
7.0 KiB
221 lines
7.0 KiB
package acr.browser.lightning.favicon; |
|
|
|
import android.app.Application; |
|
import android.graphics.Bitmap; |
|
import android.graphics.BitmapFactory; |
|
import android.net.Uri; |
|
import android.support.annotation.ColorInt; |
|
import android.support.annotation.NonNull; |
|
import android.support.annotation.Nullable; |
|
import android.support.annotation.WorkerThread; |
|
import android.text.TextUtils; |
|
import android.util.Log; |
|
import android.util.LruCache; |
|
|
|
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 javax.inject.Inject; |
|
import javax.inject.Singleton; |
|
|
|
import acr.browser.lightning.R; |
|
import acr.browser.lightning.utils.DrawableUtils; |
|
import acr.browser.lightning.utils.FileUtils; |
|
import acr.browser.lightning.utils.Preconditions; |
|
import acr.browser.lightning.utils.Utils; |
|
|
|
/** |
|
* Reactive model that can fetch favicons |
|
* from URLs and also cache them. |
|
*/ |
|
@Singleton |
|
public class FaviconModel { |
|
|
|
private static final String TAG = "FaviconModel"; |
|
|
|
@NonNull private final BitmapFactory.Options mLoaderOptions = new BitmapFactory.Options(); |
|
@NonNull private final Application mApplication; |
|
@NonNull private final LruCache<String, Bitmap> mFaviconCache = new LruCache<String, Bitmap>((int) FileUtils.megabytesToBytes(1)) { |
|
@Override |
|
protected int sizeOf(String key, Bitmap value) { |
|
return value.getByteCount(); |
|
} |
|
}; |
|
|
|
private final int mBookmarkIconSize; |
|
|
|
@Inject |
|
FaviconModel(@NonNull Application application) { |
|
mApplication = application; |
|
mBookmarkIconSize = mApplication.getResources().getDimensionPixelSize(R.dimen.bookmark_item_icon_size); |
|
} |
|
|
|
/** |
|
* Retrieves a favicon from the memory cache. |
|
* Bitmap may not be present if no bitmap has |
|
* been added for the URL or if it has been |
|
* evicted from the memory cache. |
|
* |
|
* @param url the URL to retrieve the bitmap for. |
|
* @return the bitmap associated with the URL, |
|
* may be null. |
|
*/ |
|
@Nullable |
|
private Bitmap getFaviconFromMemCache(@NonNull String url) { |
|
Preconditions.checkNonNull(url); |
|
synchronized (mFaviconCache) { |
|
return mFaviconCache.get(url); |
|
} |
|
} |
|
|
|
@NonNull |
|
public Bitmap getDefaultBitmapForString(@Nullable String title) { |
|
Character firstTitleCharacter = !TextUtils.isEmpty(title) ? title.charAt(0) : '?'; |
|
|
|
@ColorInt int defaultFaviconColor = DrawableUtils.characterToColorHash(firstTitleCharacter, mApplication); |
|
|
|
return DrawableUtils.getRoundedLetterImage(firstTitleCharacter, |
|
mBookmarkIconSize, |
|
mBookmarkIconSize, |
|
defaultFaviconColor); |
|
} |
|
|
|
/** |
|
* Adds a bitmap to the memory cache |
|
* for the given URL. |
|
* |
|
* @param url the URL to map the bitmap to. |
|
* @param bitmap the bitmap to store. |
|
*/ |
|
private void addFaviconToMemCache(@NonNull String url, @NonNull Bitmap bitmap) { |
|
Preconditions.checkNonNull(url); |
|
Preconditions.checkNonNull(bitmap); |
|
synchronized (mFaviconCache) { |
|
mFaviconCache.put(url, bitmap); |
|
} |
|
} |
|
|
|
/** |
|
* Creates the cache file for the favicon |
|
* image. File name will be in the form of |
|
* [hash of URI host].png |
|
* |
|
* @param app the context needed to retrieve the |
|
* cache directory. |
|
* @param uri the URI to use as a unique identifier. |
|
* @return a valid cache file. |
|
*/ |
|
@WorkerThread |
|
@NonNull |
|
public static File getFaviconCacheFile(@NonNull Application app, @NonNull Uri uri) { |
|
FaviconUtils.assertUriSafe(uri); |
|
|
|
String hash = String.valueOf(uri.getHost().hashCode()); |
|
|
|
return new File(app.getCacheDir(), hash + ".png"); |
|
} |
|
|
|
/** |
|
* Retrieves the favicon for a URL, |
|
* may be from network or cache. |
|
* |
|
* @param url The URL that we should retrieve the |
|
* favicon for. |
|
* @param title The title for the web page. |
|
*/ |
|
@NonNull |
|
public Single<Bitmap> faviconForUrl(@NonNull final String url, |
|
@NonNull final String title) { |
|
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(getDefaultBitmapForString(title)); |
|
|
|
subscriber.onItem(newFavicon); |
|
subscriber.onComplete(); |
|
|
|
return; |
|
} |
|
|
|
File faviconCacheFile = getFaviconCacheFile(mApplication, uri); |
|
|
|
Bitmap favicon = getFaviconFromMemCache(url); |
|
|
|
if (faviconCacheFile.exists() && favicon == null) { |
|
favicon = BitmapFactory.decodeFile(faviconCacheFile.getPath(), mLoaderOptions); |
|
|
|
if (favicon != null) { |
|
addFaviconToMemCache(url, favicon); |
|
} |
|
} |
|
|
|
if (favicon != null) { |
|
Bitmap newFavicon = Utils.padFavicon(favicon); |
|
|
|
subscriber.onItem(newFavicon); |
|
subscriber.onComplete(); |
|
|
|
return; |
|
} |
|
|
|
favicon = getDefaultBitmapForString(title); |
|
|
|
Bitmap newFavicon = Utils.padFavicon(favicon); |
|
|
|
subscriber.onItem(newFavicon); |
|
subscriber.onComplete(); |
|
} |
|
}); |
|
} |
|
|
|
/** |
|
* Caches a favicon for a particular URL. |
|
* |
|
* @param favicon the favicon to cache. |
|
* @param url the URL to cache the favicon for. |
|
* @return an observable that notifies the consumer |
|
* when it is complete. |
|
*/ |
|
@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; |
|
} |
|
|
|
Log.d(TAG, "Caching icon for " + uri.getHost()); |
|
FileOutputStream fos = null; |
|
|
|
try { |
|
File image = getFaviconCacheFile(mApplication, 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); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
}
|
|
|