Cleaning up image request logic
This commit is contained in:
parent
1d6ef194d1
commit
cca39aa3d2
@ -7,7 +7,6 @@ import acr.browser.lightning.activity.ReadingActivity;
|
|||||||
import acr.browser.lightning.activity.TabsManager;
|
import acr.browser.lightning.activity.TabsManager;
|
||||||
import acr.browser.lightning.activity.ThemableBrowserActivity;
|
import acr.browser.lightning.activity.ThemableBrowserActivity;
|
||||||
import acr.browser.lightning.activity.ThemableSettingsActivity;
|
import acr.browser.lightning.activity.ThemableSettingsActivity;
|
||||||
import acr.browser.lightning.view.ImageDownloader;
|
|
||||||
import acr.browser.lightning.browser.BrowserPresenter;
|
import acr.browser.lightning.browser.BrowserPresenter;
|
||||||
import acr.browser.lightning.constant.BookmarkPage;
|
import acr.browser.lightning.constant.BookmarkPage;
|
||||||
import acr.browser.lightning.constant.HistoryPage;
|
import acr.browser.lightning.constant.HistoryPage;
|
||||||
@ -77,6 +76,4 @@ public interface AppComponent {
|
|||||||
|
|
||||||
void inject(SuggestionsAdapter suggestionsAdapter);
|
void inject(SuggestionsAdapter suggestionsAdapter);
|
||||||
|
|
||||||
void inject(ImageDownloader imageDownloader);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 @@
|
|||||||
|
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 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -45,7 +45,7 @@ import acr.browser.lightning.R;
|
|||||||
import acr.browser.lightning.activity.ReadingActivity;
|
import acr.browser.lightning.activity.ReadingActivity;
|
||||||
import acr.browser.lightning.activity.TabsManager;
|
import acr.browser.lightning.activity.TabsManager;
|
||||||
import acr.browser.lightning.app.BrowserApp;
|
import acr.browser.lightning.app.BrowserApp;
|
||||||
import acr.browser.lightning.view.ImageDownloader;
|
import acr.browser.lightning.favicon.FaviconModel;
|
||||||
import acr.browser.lightning.browser.BookmarksView;
|
import acr.browser.lightning.browser.BookmarksView;
|
||||||
import acr.browser.lightning.bus.BookmarkEvents;
|
import acr.browser.lightning.bus.BookmarkEvents;
|
||||||
import acr.browser.lightning.constant.Constants;
|
import acr.browser.lightning.constant.Constants;
|
||||||
@ -74,7 +74,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
|||||||
|
|
||||||
@Inject PreferenceManager mPreferenceManager;
|
@Inject PreferenceManager mPreferenceManager;
|
||||||
|
|
||||||
private ImageDownloader mImageDownloader;
|
private FaviconModel mFaviconModel;
|
||||||
|
|
||||||
private TabsManager mTabsManager;
|
private TabsManager mTabsManager;
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
|||||||
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(context) :
|
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(context) :
|
||||||
ThemeUtils.getIconLightThemeColor(context);
|
ThemeUtils.getIconLightThemeColor(context);
|
||||||
|
|
||||||
mImageDownloader = new ImageDownloader(mWebpageBitmap);
|
mFaviconModel = new FaviconModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private TabsManager getTabsManager() {
|
private TabsManager getTabsManager() {
|
||||||
@ -228,7 +228,7 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
|||||||
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(activity) :
|
mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(activity) :
|
||||||
ThemeUtils.getIconLightThemeColor(activity);
|
ThemeUtils.getIconLightThemeColor(activity);
|
||||||
|
|
||||||
mImageDownloader = new ImageDownloader(mWebpageBitmap);
|
mFaviconModel = new FaviconModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateBookmarkIndicator(final String url) {
|
private void updateBookmarkIndicator(final String url) {
|
||||||
@ -405,7 +405,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener,
|
|||||||
|
|
||||||
final String url = web.getUrl();
|
final String url = web.getUrl();
|
||||||
final WeakReference<ImageView> imageViewReference = new WeakReference<>(holder.favicon);
|
final WeakReference<ImageView> imageViewReference = new WeakReference<>(holder.favicon);
|
||||||
mImageDownloader.newImageRequest(url)
|
|
||||||
|
mFaviconModel.faviconForUrl(url, mWebpageBitmap, true)
|
||||||
.subscribeOn(Schedulers.worker())
|
.subscribeOn(Schedulers.worker())
|
||||||
.observeOn(Schedulers.main())
|
.observeOn(Schedulers.main())
|
||||||
.subscribe(new SingleOnSubscribe<Bitmap>() {
|
.subscribe(new SingleOnSubscribe<Bitmap>() {
|
||||||
|
@ -14,6 +14,7 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.LinearGradient;
|
import android.graphics.LinearGradient;
|
||||||
@ -74,7 +75,7 @@ public final class Utils {
|
|||||||
public static void downloadFile(final Activity activity, final PreferenceManager manager, final String url,
|
public static void downloadFile(final Activity activity, final PreferenceManager manager, final String url,
|
||||||
final String userAgent, final String contentDisposition) {
|
final String userAgent, final String contentDisposition) {
|
||||||
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
PermissionsManager.getInstance().requestPermissionsIfNecessaryForResult(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsResultAction() {
|
Manifest.permission.WRITE_EXTERNAL_STORAGE}, new PermissionsResultAction() {
|
||||||
@Override
|
@Override
|
||||||
public void onGranted() {
|
public void onGranted() {
|
||||||
String fileName = URLUtil.guessFileName(url, null, null);
|
String fileName = URLUtil.guessFileName(url, null, null);
|
||||||
@ -124,13 +125,13 @@ public final class Utils {
|
|||||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
builder.setTitle(title);
|
builder.setTitle(title);
|
||||||
builder.setMessage(message)
|
builder.setMessage(message)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(activity.getResources().getString(R.string.action_ok),
|
.setPositiveButton(activity.getResources().getString(R.string.action_ok),
|
||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AlertDialog alert = builder.create();
|
AlertDialog alert = builder.create();
|
||||||
alert.show();
|
alert.show();
|
||||||
BrowserDialog.setDialogSize(activity, alert);
|
BrowserDialog.setDialogSize(activity, alert);
|
||||||
@ -259,7 +260,7 @@ public final class Utils {
|
|||||||
int padding = Utils.dpToPx(4);
|
int padding = Utils.dpToPx(4);
|
||||||
|
|
||||||
Bitmap paddedBitmap = Bitmap.createBitmap(bitmap.getWidth() + padding, bitmap.getHeight()
|
Bitmap paddedBitmap = Bitmap.createBitmap(bitmap.getWidth() + padding, bitmap.getHeight()
|
||||||
+ padding, Bitmap.Config.ARGB_8888);
|
+ padding, Bitmap.Config.ARGB_8888);
|
||||||
|
|
||||||
Canvas canvas = new Canvas(paddedBitmap);
|
Canvas canvas = new Canvas(paddedBitmap);
|
||||||
canvas.drawARGB(0x00, 0x00, 0x00, 0x00); // this represents white color
|
canvas.drawARGB(0x00, 0x00, 0x00, 0x00); // this represents white color
|
||||||
@ -303,10 +304,10 @@ public final class Utils {
|
|||||||
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
|
||||||
String imageFileName = "JPEG_" + timeStamp + '_';
|
String imageFileName = "JPEG_" + timeStamp + '_';
|
||||||
File storageDir = Environment
|
File storageDir = Environment
|
||||||
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
||||||
return File.createTempFile(imageFileName, /* prefix */
|
return File.createTempFile(imageFileName, /* prefix */
|
||||||
".jpg", /* suffix */
|
".jpg", /* suffix */
|
||||||
storageDir /* directory */
|
storageDir /* directory */
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,9 +381,9 @@ public final class Utils {
|
|||||||
paint.setDither(true);
|
paint.setDither(true);
|
||||||
if (withShader) {
|
if (withShader) {
|
||||||
paint.setShader(new LinearGradient(0, 0.9f * canvas.getHeight(),
|
paint.setShader(new LinearGradient(0, 0.9f * canvas.getHeight(),
|
||||||
0, canvas.getHeight(),
|
0, canvas.getHeight(),
|
||||||
color, mixTwoColors(Color.BLACK, color, 0.5f),
|
color, mixTwoColors(Color.BLACK, color, 0.5f),
|
||||||
Shader.TileMode.CLAMP));
|
Shader.TileMode.CLAMP));
|
||||||
} else {
|
} else {
|
||||||
paint.setShader(null);
|
paint.setShader(null);
|
||||||
}
|
}
|
||||||
@ -431,4 +432,27 @@ public final class Utils {
|
|||||||
Utils.showSnackbar(activity, R.string.message_added_to_homescreen);
|
Utils.showSnackbar(activity, R.string.message_added_to_homescreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int calculateInSampleSize(@NonNull BitmapFactory.Options options,
|
||||||
|
int reqWidth, int reqHeight) {
|
||||||
|
// Raw height and width of image
|
||||||
|
final int height = options.outHeight;
|
||||||
|
final int width = options.outWidth;
|
||||||
|
int inSampleSize = 1;
|
||||||
|
|
||||||
|
if (height > reqHeight || width > reqWidth) {
|
||||||
|
|
||||||
|
final int halfHeight = height / 2;
|
||||||
|
final int halfWidth = width / 2;
|
||||||
|
|
||||||
|
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||||
|
// height and width larger than the requested height and width.
|
||||||
|
while ((halfHeight / inSampleSize) >= reqHeight
|
||||||
|
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||||
|
inSampleSize *= 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inSampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -2,7 +2,6 @@ package acr.browser.lightning.view;
|
|||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@ -19,13 +18,14 @@ import android.webkit.ValueCallback;
|
|||||||
import android.webkit.WebChromeClient;
|
import android.webkit.WebChromeClient;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import com.anthonycr.bonsai.Schedulers;
|
||||||
import com.anthonycr.grant.PermissionsManager;
|
import com.anthonycr.grant.PermissionsManager;
|
||||||
import com.anthonycr.grant.PermissionsResultAction;
|
import com.anthonycr.grant.PermissionsResultAction;
|
||||||
|
|
||||||
import acr.browser.lightning.R;
|
import acr.browser.lightning.R;
|
||||||
import acr.browser.lightning.app.BrowserApp;
|
|
||||||
import acr.browser.lightning.controller.UIController;
|
import acr.browser.lightning.controller.UIController;
|
||||||
import acr.browser.lightning.dialog.BrowserDialog;
|
import acr.browser.lightning.dialog.BrowserDialog;
|
||||||
|
import acr.browser.lightning.favicon.FaviconModel;
|
||||||
import acr.browser.lightning.utils.Preconditions;
|
import acr.browser.lightning.utils.Preconditions;
|
||||||
|
|
||||||
class LightningChromeClient extends WebChromeClient {
|
class LightningChromeClient extends WebChromeClient {
|
||||||
@ -37,6 +37,7 @@ class LightningChromeClient extends WebChromeClient {
|
|||||||
@NonNull private final Activity mActivity;
|
@NonNull private final Activity mActivity;
|
||||||
@NonNull private final LightningView mLightningView;
|
@NonNull private final LightningView mLightningView;
|
||||||
@NonNull private final UIController mUIController;
|
@NonNull private final UIController mUIController;
|
||||||
|
@NonNull private final FaviconModel mFaviconModel;
|
||||||
|
|
||||||
LightningChromeClient(@NonNull Activity activity, @NonNull LightningView lightningView) {
|
LightningChromeClient(@NonNull Activity activity, @NonNull LightningView lightningView) {
|
||||||
Preconditions.checkNonNull(activity);
|
Preconditions.checkNonNull(activity);
|
||||||
@ -44,6 +45,7 @@ class LightningChromeClient extends WebChromeClient {
|
|||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
mUIController = (UIController) activity;
|
mUIController = (UIController) activity;
|
||||||
mLightningView = lightningView;
|
mLightningView = lightningView;
|
||||||
|
mFaviconModel = new FaviconModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -57,7 +59,7 @@ class LightningChromeClient extends WebChromeClient {
|
|||||||
public void onReceivedIcon(@NonNull WebView view, Bitmap icon) {
|
public void onReceivedIcon(@NonNull WebView view, Bitmap icon) {
|
||||||
mLightningView.getTitleInfo().setFavicon(icon);
|
mLightningView.getTitleInfo().setFavicon(icon);
|
||||||
mUIController.tabChanged(mLightningView);
|
mUIController.tabChanged(mLightningView);
|
||||||
cacheFavicon(view.getUrl(), icon, mActivity);
|
cacheFavicon(view.getUrl(), icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -65,13 +67,20 @@ class LightningChromeClient extends WebChromeClient {
|
|||||||
*
|
*
|
||||||
* @param icon the icon to cache
|
* @param icon the icon to cache
|
||||||
*/
|
*/
|
||||||
private static void cacheFavicon(@Nullable final String url, @Nullable final Bitmap icon, @NonNull final Context context) {
|
private void cacheFavicon(@Nullable final String url, @Nullable final Bitmap icon) {
|
||||||
if (icon == null || url == null) return;
|
if (icon == null || url == null) {
|
||||||
final Uri uri = Uri.parse(url);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri = Uri.parse(url);
|
||||||
|
|
||||||
if (uri.getHost() == null) {
|
if (uri.getHost() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BrowserApp.getIOThread().execute(new IconCacheTask(uri, icon, BrowserApp.get(context)));
|
|
||||||
|
mFaviconModel.cacheFaviconForUrl(icon, url)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user