diff --git a/app/build.gradle b/app/build.gradle index 19720c7..161c194 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -66,7 +66,7 @@ dexcount { dependencies { // support libraries - def supportLibVersion = '25.3.0' + def supportLibVersion = '25.3.1' compile "com.android.support:palette-v7:$supportLibVersion" compile "com.android.support:appcompat-v7:$supportLibVersion" compile "com.android.support:design:$supportLibVersion" diff --git a/app/src/main/java/acr/browser/lightning/app/AppComponent.java b/app/src/main/java/acr/browser/lightning/app/AppComponent.java index c807078..fd2b204 100644 --- a/app/src/main/java/acr/browser/lightning/app/AppComponent.java +++ b/app/src/main/java/acr/browser/lightning/app/AppComponent.java @@ -7,6 +7,7 @@ import acr.browser.lightning.activity.ReadingActivity; import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.activity.ThemableBrowserActivity; import acr.browser.lightning.activity.ThemableSettingsActivity; +import acr.browser.lightning.view.ImageDownloader; import acr.browser.lightning.browser.BrowserPresenter; import acr.browser.lightning.constant.BookmarkPage; import acr.browser.lightning.constant.HistoryPage; @@ -76,4 +77,6 @@ public interface AppComponent { void inject(SuggestionsAdapter suggestionsAdapter); + void inject(ImageDownloader imageDownloader); + } diff --git a/app/src/main/java/acr/browser/lightning/async/AsyncExecutor.java b/app/src/main/java/acr/browser/lightning/async/AsyncExecutor.java deleted file mode 100644 index 7dea224..0000000 --- a/app/src/main/java/acr/browser/lightning/async/AsyncExecutor.java +++ /dev/null @@ -1,53 +0,0 @@ -package acr.browser.lightning.async; - -import android.support.annotation.NonNull; -import android.util.Log; - -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.RejectedExecutionException; - -/** - * Created 9/27/2015 Anthony Restaino - */ -public class AsyncExecutor implements Executor { - - private static final String TAG = AsyncExecutor.class.getSimpleName(); - private static final AsyncExecutor INSTANCE = new AsyncExecutor(); - private final Queue mQueue = new ArrayDeque<>(1); - private final ExecutorService mExecutor = Executors.newFixedThreadPool(4); - - private AsyncExecutor() {} - - @NonNull - public static AsyncExecutor getInstance() { - return INSTANCE; - } - - public synchronized void notifyThreadFinish() { - if (mQueue.isEmpty()) { - return; - } - Runnable runnable = mQueue.remove(); - execute(runnable); - } - - @Override - protected void finalize() throws Throwable { - mExecutor.shutdownNow(); - super.finalize(); - } - - @Override - public void execute(@NonNull Runnable command) { - try { - mExecutor.execute(command); - } catch (RejectedExecutionException ignored) { - mQueue.add(command); - Log.d(TAG, "Thread was enqueued"); - } - } -} diff --git a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java index 4da8414..ed06805 100644 --- a/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java +++ b/app/src/main/java/acr/browser/lightning/fragment/BookmarksFragment.java @@ -35,6 +35,7 @@ import com.anthonycr.bonsai.SingleSubscriber; import com.squareup.otto.Bus; import com.squareup.otto.Subscribe; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; @@ -44,8 +45,7 @@ import acr.browser.lightning.R; import acr.browser.lightning.activity.ReadingActivity; import acr.browser.lightning.activity.TabsManager; import acr.browser.lightning.app.BrowserApp; -import acr.browser.lightning.async.AsyncExecutor; -import acr.browser.lightning.async.ImageDownloadTask; +import acr.browser.lightning.view.ImageDownloader; import acr.browser.lightning.browser.BookmarksView; import acr.browser.lightning.bus.BookmarkEvents; import acr.browser.lightning.constant.Constants; @@ -74,6 +74,8 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, @Inject PreferenceManager mPreferenceManager; + private ImageDownloader mImageDownloader; + private TabsManager mTabsManager; private UIController mUiController; @@ -124,7 +126,9 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, mWebpageBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); mFolderBitmap = ThemeUtils.getThemedBitmap(context, R.drawable.ic_folder, darkTheme); mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(context) : - ThemeUtils.getIconLightThemeColor(context); + ThemeUtils.getIconLightThemeColor(context); + + mImageDownloader = new ImageDownloader(mWebpageBitmap); } private TabsManager getTabsManager() { @@ -222,7 +226,9 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, mWebpageBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_webpage, darkTheme); mFolderBitmap = ThemeUtils.getThemedBitmap(activity, R.drawable.ic_folder, darkTheme); mIconColor = darkTheme ? ThemeUtils.getIconDarkThemeColor(activity) : - ThemeUtils.getIconLightThemeColor(activity); + ThemeUtils.getIconLightThemeColor(activity); + + mImageDownloader = new ImageDownloader(mWebpageBitmap); } private void updateBookmarkIndicator(final String url) { @@ -389,14 +395,31 @@ public class BookmarksFragment extends Fragment implements View.OnClickListener, ViewCompat.jumpDrawablesToCurrentState(row); - HistoryItem web = mBookmarks.get(position); + final HistoryItem web = mBookmarks.get(position); holder.txtTitle.setText(web.getTitle()); if (web.isFolder()) { holder.favicon.setImageBitmap(mFolderBitmap); } else if (web.getBitmap() == null) { holder.favicon.setImageBitmap(mWebpageBitmap); - new ImageDownloadTask(holder.favicon, web, mWebpageBitmap, BrowserApp.get(context)) - .executeOnExecutor(AsyncExecutor.getInstance()); + holder.favicon.setTag(web.getUrl().hashCode()); + + final String url = web.getUrl(); + final WeakReference imageViewReference = new WeakReference<>(holder.favicon); + mImageDownloader.newImageRequest(url) + .subscribeOn(Schedulers.worker()) + .observeOn(Schedulers.main()) + .subscribe(new SingleOnSubscribe() { + @Override + public void onItem(@Nullable Bitmap item) { + ImageView imageView = imageViewReference.get(); + Object tag = imageView != null ? imageView.getTag() : null; + if (tag != null && tag.equals(url.hashCode())) { + imageView.setImageBitmap(item); + } + + web.setBitmap(item); + } + }); } else { holder.favicon.setImageBitmap(web.getBitmap()); } diff --git a/app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java b/app/src/main/java/acr/browser/lightning/view/ImageDownloader.java similarity index 51% rename from app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java rename to app/src/main/java/acr/browser/lightning/view/ImageDownloader.java index 60cd9bf..61378c7 100644 --- a/app/src/main/java/acr/browser/lightning/async/ImageDownloadTask.java +++ b/app/src/main/java/acr/browser/lightning/view/ImageDownloader.java @@ -1,69 +1,91 @@ -package acr.browser.lightning.async; +package acr.browser.lightning.view; import android.app.Application; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.AsyncTask; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.util.Log; -import android.widget.ImageView; -import com.anthonycr.bonsai.Schedulers; +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.InputStream; -import java.lang.ref.WeakReference; 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.database.HistoryItem; import acr.browser.lightning.utils.Utils; -public class ImageDownloadTask extends AsyncTask { - - private static final String TAG = ImageDownloadTask.class.getSimpleName(); - @NonNull private final WeakReference mFaviconImage; - @NonNull private final Application mContext; - @NonNull private final HistoryItem mWeb; - private final String mUrl; - @NonNull private final Bitmap mDefaultBitmap; - - public ImageDownloadTask(@NonNull ImageView bmImage, - @NonNull HistoryItem web, - @NonNull Bitmap defaultBitmap, - @NonNull Application context) { - // Set a tag on the ImageView so we know if the view - // has gone out of scope and should not be used - bmImage.setTag(web.getUrl().hashCode()); - this.mFaviconImage = new WeakReference<>(bmImage); - this.mWeb = web; - this.mUrl = web.getUrl(); - this.mDefaultBitmap = defaultBitmap; - this.mContext = context; +/** + * An ImageDownloader that creates image + * loading requests on demand. + */ +public class ImageDownloader { + + private static final String TAG = "ImageDownloader"; + + @Inject Application mApp; + + @NonNull private Bitmap mDefaultBitmap; + + public ImageDownloader(@NonNull Bitmap defaultBitmap) { + mDefaultBitmap = defaultBitmap; + BrowserApp.getAppComponent().inject(this); + } + + /** + * 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 newImageRequest(@Nullable final String url) { + return Single.create(new SingleAction() { + @Override + public void onSubscribe(@NonNull SingleSubscriber subscriber) { + Bitmap favicon = retrieveBitmap(mApp, mDefaultBitmap, url); + + Bitmap paddedFavicon = Utils.padFavicon(favicon); + + subscriber.onItem(paddedFavicon); + subscriber.onComplete(); + } + }); } @NonNull - @Override - protected Bitmap doInBackground(Void... params) { - Bitmap mIcon = null; + private static Bitmap retrieveBitmap(@NonNull Application app, + @NonNull Bitmap defaultBitmap, + @Nullable String url) { + // unique path for each url that is bookmarked. - if (mUrl == null) { - return mDefaultBitmap; + if (url == null) { + return defaultBitmap; } - File cache = mContext.getCacheDir(); - final Uri uri = Uri.parse(mUrl); - if (uri.getHost() == null || uri.getScheme() == null) { - return mDefaultBitmap; + + Bitmap icon = null; + File cache = app.getCacheDir(); + final Uri uri = Uri.parse(url); + + if (uri.getHost() == null || uri.getScheme() == null || Constants.FILE.startsWith(uri.getScheme())) { + return defaultBitmap; } + final String hash = String.valueOf(uri.getHost().hashCode()); final File image = new File(cache, hash + ".png"); final String urlDisplay = uri.getScheme() + "://" + uri.getHost() + "/favicon.ico"; - if (Constants.FILE.startsWith(uri.getScheme())) { - return mDefaultBitmap; - } + // checks to see if the image exists if (!image.exists()) { FileOutputStream fos = null; @@ -79,12 +101,12 @@ public class ImageDownloadTask extends AsyncTask { in = connection.getInputStream(); if (in != null) { - mIcon = BitmapFactory.decodeStream(in); + icon = BitmapFactory.decodeStream(in); } // ...and cache it - if (mIcon != null) { + if (icon != null) { fos = new FileOutputStream(image); - mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos); + icon.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); Log.d(Constants.TAG, "Downloaded: " + urlDisplay); } @@ -97,9 +119,10 @@ public class ImageDownloadTask extends AsyncTask { } } else { // if it exists, retrieve it from the cache - mIcon = BitmapFactory.decodeFile(image.getPath()); + icon = BitmapFactory.decodeFile(image.getPath()); } - if (mIcon == null) { + + if (icon == null) { InputStream in = null; FileOutputStream fos = null; try { @@ -113,12 +136,12 @@ public class ImageDownloadTask extends AsyncTask { in = connection.getInputStream(); if (in != null) { - mIcon = BitmapFactory.decodeStream(in); + icon = BitmapFactory.decodeStream(in); } // ...and cache it - if (mIcon != null) { + if (icon != null) { fos = new FileOutputStream(image); - mIcon.compress(Bitmap.CompressFormat.PNG, 100, fos); + icon.compress(Bitmap.CompressFormat.PNG, 100, fos); fos.flush(); } @@ -129,28 +152,11 @@ public class ImageDownloadTask extends AsyncTask { Utils.close(fos); } } - if (mIcon == null) { - return mDefaultBitmap; - } else { - return mIcon; - } - } - @Override - protected void onPostExecute(Bitmap bitmap) { - super.onPostExecute(bitmap); - AsyncExecutor.getInstance().notifyThreadFinish(); - final Bitmap fav = Utils.padFavicon(bitmap); - final ImageView view = mFaviconImage.get(); - if (view != null && view.getTag().equals(mWeb.getUrl().hashCode())) { - Schedulers.main().execute(new Runnable() { - @Override - public void run() { - view.setImageBitmap(fav); - } - }); + if (icon == null) { + return defaultBitmap; + } else { + return icon; } - mWeb.setBitmap(fav); } - }