Commit 8a7888ec authored by Brandon Wylie's avatar Brandon Wylie Committed by Commit Bot

[Image Fetcher] Adding ImageFetcherFactory to construct instances

Switching from having each ImageFetcher-derived class owning its own
singleton to a factory pattern. Moved resizing logic to the bridge.

Bug: 944520
Change-Id: Ib4315d042aa4b38f46469c8b6367fb77e5dfb34d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1534793
Commit-Queue: Brandon Wylie <wylieb@chromium.org>
Reviewed-by: default avatarFilip Gorski <fgorski@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#647072}
parent f0bc90f4
......@@ -779,8 +779,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/identity/UniqueIdentificationGeneratorFactory.java",
"java/src/org/chromium/chrome/browser/identity/UuidBasedUniqueIdentificationGenerator.java",
"java/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcher.java",
"java/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherImpl.java",
"java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcherBridge.java",
"java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcherFactory.java",
"java/src/org/chromium/chrome/browser/image_fetcher/ImageFetcher.java",
"java/src/org/chromium/chrome/browser/image_fetcher/InMemoryCachedImageFetcher.java",
"java/src/org/chromium/chrome/browser/incognito/IncognitoDisclosureActivity.java",
"java/src/org/chromium/chrome/browser/incognito/IncognitoNotificationManager.java",
......
......@@ -87,7 +87,9 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/fullscreen/BrowserStateBrowserControlsVisibilityDelegateTest.java",
"junit/src/org/chromium/chrome/browser/fullscreen/TokenHolderTest.java",
"junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java",
"junit/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherImplTest.java",
"junit/src/org/chromium/chrome/browser/image_fetcher/CachedImageFetcherTest.java",
"junit/src/org/chromium/chrome/browser/image_fetcher/ImageFetcherFactoryTest.java",
"junit/src/org/chromium/chrome/browser/image_fetcher/ImageFetcherTest.java",
"junit/src/org/chromium/chrome/browser/image_fetcher/InMemoryCachedImageFetcherTest.java",
"junit/src/org/chromium/chrome/browser/incognito/IncognitoTabSnapshotControllerTest.java",
"junit/src/org/chromium/chrome/browser/infobar/IPHInfoBarSupportTest.java",
......@@ -167,7 +169,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/signin/SigninManagerTest.java",
"junit/src/org/chromium/chrome/browser/signin/SigninPromoUtilTest.java",
"junit/src/org/chromium/chrome/browser/snackbar/SnackbarCollectionUnitTest.java",
"junit/src/org/chromium/chrome/browser/suggestions/ImageFetcherTest.java",
"junit/src/org/chromium/chrome/browser/suggestions/SuggestionsImageFetcherTest.java",
"junit/src/org/chromium/chrome/browser/suggestions/TileGroupUnitTest.java",
"junit/src/org/chromium/chrome/browser/survey/ChromeSurveyControllerTest.java",
"junit/src/org/chromium/chrome/browser/tab/TabAttributesTest.java",
......
......@@ -43,8 +43,8 @@ class AssistantBottomBarCoordinator {
private final int mBottomBarWithoutIndicatorPaddingTop;
// Child coordinators.
private AssistantInfoBoxCoordinator mInfoBoxCoordinator;
private final AssistantHeaderCoordinator mHeaderCoordinator;
private final AssistantInfoBoxCoordinator mInfoBoxCoordinator;
private final AssistantDetailsCoordinator mDetailsCoordinator;
private final AssistantPaymentRequestCoordinator mPaymentRequestCoordinator;
private final AssistantCarouselCoordinator mSuggestionsCoordinator;
......@@ -102,6 +102,14 @@ class AssistantBottomBarCoordinator {
setHorizontalMargins(mPaymentRequestCoordinator.getView());
}
/**
* Cleanup resources when this goes out of scope.
*/
public void destroy() {
mInfoBoxCoordinator.destroy();
mInfoBoxCoordinator = null;
}
/**
* Return the container view representing the bottom bar. Adding child views to this view should
* add them below the header.
......
......@@ -39,7 +39,7 @@ class AssistantCoordinator {
private final AssistantModel mModel;
private final View mAssistantView;
private final AssistantBottomBarCoordinator mBottomBarCoordinator;
private AssistantBottomBarCoordinator mBottomBarCoordinator;
private final AssistantKeyboardCoordinator mKeyboardCoordinator;
private final AssistantOverlayCoordinator mOverlayCoordinator;
......@@ -92,6 +92,8 @@ class AssistantCoordinator {
setVisible(false);
detachAssistantView();
mOverlayCoordinator.destroy();
mBottomBarCoordinator.destroy();
mBottomBarCoordinator = null;
}
/**
......
......@@ -29,7 +29,9 @@ import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.compositor.animation.CompositorAnimator;
import org.chromium.chrome.browser.customtabs.CustomTabActivity;
import org.chromium.chrome.browser.image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory;
import org.chromium.chrome.browser.modaldialog.AppModalPresenter;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;
......@@ -93,6 +95,7 @@ class AssistantDetailsViewBinder
private final int mPulseAnimationEndColor;
private ValueAnimator mPulseAnimation;
private ImageFetcher mImageFetcher;
AssistantDetailsViewBinder(Context context) {
mContext = context;
......@@ -102,6 +105,15 @@ class AssistantDetailsViewBinder
R.dimen.autofill_assistant_details_image_size);
mPulseAnimationStartColor = context.getResources().getColor(R.color.modern_grey_300);
mPulseAnimationEndColor = context.getResources().getColor(R.color.modern_grey_200);
mImageFetcher = ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.DISK_CACHE_ONLY);
}
/**
* Cleanup resources when this goes out of scope.
*/
void destroy() {
mImageFetcher.destroy();
mImageFetcher = null;
}
@Override
......@@ -156,8 +168,8 @@ class AssistantDetailsViewBinder
}
} else {
// Download image and then set it in the view.
CachedImageFetcher.getInstance().fetchImage(details.getImageUrl(),
CachedImageFetcher.ASSISTANT_DETAILS_UMA_CLIENT_NAME, image -> {
mImageFetcher.fetchImage(details.getImageUrl(),
ImageFetcher.ASSISTANT_DETAILS_UMA_CLIENT_NAME, image -> {
if (image != null) {
viewHolder.mImageView.setImageDrawable(getRoundedImage(image));
if (details.getAllowImageClickthrough()) {
......
......@@ -17,13 +17,14 @@ import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
*/
public class AssistantInfoBoxCoordinator {
private final View mView;
private AssistantInfoBoxViewBinder mViewBinder;
public AssistantInfoBoxCoordinator(Context context, AssistantInfoBoxModel model) {
mView = LayoutInflater.from(context).inflate(
R.layout.autofill_assistant_info_box, /* root= */ null);
ViewHolder viewHolder = new ViewHolder(context, mView);
AssistantInfoBoxViewBinder viewBinder = new AssistantInfoBoxViewBinder(context);
PropertyModelChangeProcessor.create(model, viewHolder, viewBinder);
mViewBinder = new AssistantInfoBoxViewBinder(context);
PropertyModelChangeProcessor.create(model, viewHolder, mViewBinder);
// InfoBox view is initially hidden.
setVisible(false);
......@@ -41,6 +42,14 @@ public class AssistantInfoBoxCoordinator {
});
}
/**
* Explicitly clean up.
*/
public void destroy() {
mViewBinder.destroy();
mViewBinder = null;
}
/**
* Return the view associated to the info box.
*/
......
......@@ -12,7 +12,9 @@ import android.view.View;
import android.widget.TextView;
import org.chromium.chrome.autofill_assistant.R;
import org.chromium.chrome.browser.image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModelChangeProcessor;
......@@ -36,9 +38,19 @@ class AssistantInfoBoxViewBinder
}
private final Context mContext;
private ImageFetcher mImageFetcher;
AssistantInfoBoxViewBinder(Context context) {
mContext = context;
mImageFetcher = ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.DISK_CACHE_ONLY);
}
/**
* Explicitly clean up.
*/
public void destroy() {
mImageFetcher.destroy();
mImageFetcher = null;
}
@Override
......@@ -62,8 +74,8 @@ class AssistantInfoBoxViewBinder
viewHolder.mExplanationView.setCompoundDrawablesWithIntrinsicBounds(
null, AppCompatResources.getDrawable(mContext, R.drawable.ic_tick), null, null);
} else {
CachedImageFetcher.getInstance().fetchImage(infoBox.getImagePath(),
CachedImageFetcher.ASSISTANT_INFO_BOX_UMA_CLIENT_NAME, image -> {
mImageFetcher.fetchImage(infoBox.getImagePath(),
ImageFetcher.ASSISTANT_INFO_BOX_UMA_CLIENT_NAME, image -> {
if (image != null) {
Drawable d = new BitmapDrawable(mContext.getResources(), image);
viewHolder.mExplanationView.setCompoundDrawablesWithIntrinsicBounds(
......
......@@ -23,8 +23,9 @@ import org.chromium.base.SysUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.image_fetcher.InMemoryCachedImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory;
import org.chromium.chrome.browser.suggestions.ThumbnailGradient;
import org.chromium.content_public.browser.UiThreadTaskTraits;
......@@ -43,7 +44,7 @@ public class FeedImageLoader implements ImageLoaderApi {
private static final String OVERLAY_IMAGE_DIRECTION_END = "end";
private Context mActivityContext;
private CachedImageFetcher mCachedImageFetcher;
private ImageFetcher mImageFetcher;
/**
* Creates a FeedImageLoader for fetching image for the current user.
......@@ -52,16 +53,15 @@ public class FeedImageLoader implements ImageLoaderApi {
*/
public FeedImageLoader(Context activityContext, DiscardableReferencePool referencePool) {
mActivityContext = activityContext;
if (SysUtils.isLowEndDevice()) {
mCachedImageFetcher = CachedImageFetcher.getInstance();
} else {
mCachedImageFetcher = new InMemoryCachedImageFetcher(referencePool);
}
mImageFetcher = ImageFetcherFactory.createImageFetcher(SysUtils.isLowEndDevice()
? ImageFetcherConfig.DISK_CACHE_ONLY
: ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
referencePool);
}
public void destroy() {
mCachedImageFetcher.destroy();
mCachedImageFetcher = null;
mImageFetcher.destroy();
mImageFetcher = null;
}
@Override
......@@ -83,7 +83,7 @@ public class FeedImageLoader implements ImageLoaderApi {
*/
private void loadDrawableWithIter(
Iterator<String> urlsIter, int widthPx, int heightPx, Consumer<Drawable> consumer) {
if (!urlsIter.hasNext() || mCachedImageFetcher == null) {
if (!urlsIter.hasNext() || mImageFetcher == null) {
// Post to ensure callback is not run synchronously.
PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> consumer.accept(null));
return;
......@@ -168,13 +168,12 @@ public class FeedImageLoader implements ImageLoaderApi {
@VisibleForTesting
protected void fetchImage(String url, int width, int height, Callback<Bitmap> callback) {
mCachedImageFetcher.fetchImage(
url, CachedImageFetcher.FEED_UMA_CLIENT_NAME, width, height, callback);
mImageFetcher.fetchImage(url, ImageFetcher.FEED_UMA_CLIENT_NAME, width, height, callback);
}
@VisibleForTesting
FeedImageLoader(Context activityContext, CachedImageFetcher cachedImageFetcher) {
FeedImageLoader(Context activityContext, ImageFetcher imageFetcher) {
mActivityContext = activityContext;
mCachedImageFetcher = cachedImageFetcher;
mImageFetcher = imageFetcher;
}
}
......@@ -11,7 +11,9 @@ import org.chromium.base.VisibleForTesting;
import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsBridge.ContextualSuggestionsResult;
import org.chromium.chrome.browser.image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory;
import org.chromium.chrome.browser.ntp.snippets.EmptySuggestionsSource;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
import org.chromium.chrome.browser.profiles.Profile;
......@@ -23,7 +25,7 @@ import org.chromium.content_public.browser.WebContents;
class ContextualSuggestionsSourceImpl
extends EmptySuggestionsSource implements ContextualSuggestionsSource {
private ContextualSuggestionsBridge mBridge;
private CachedImageFetcher mCachedImageFetcher;
private ImageFetcher mImageFetcher;
/**
* Creates a ContextualSuggestionsSource for getting contextual suggestions for the current
......@@ -33,19 +35,22 @@ class ContextualSuggestionsSourceImpl
*/
public ContextualSuggestionsSourceImpl(Profile profile) {
mBridge = new ContextualSuggestionsBridge(profile);
mCachedImageFetcher = CachedImageFetcher.getInstance();
mImageFetcher = ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.DISK_CACHE_ONLY);
}
@Override
public void destroy() {
mBridge.destroy();
mBridge = null;
mImageFetcher.destroy();
mImageFetcher = null;
}
@Override
public void fetchSuggestionImage(SnippetArticle suggestion, Callback<Bitmap> callback) {
String url = mBridge.getImageUrl(suggestion);
mCachedImageFetcher.fetchImage(
url, CachedImageFetcher.CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME, callback);
mImageFetcher.fetchImage(
url, ImageFetcher.CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME, callback);
}
@Override
......@@ -57,8 +62,8 @@ class ContextualSuggestionsSourceImpl
return;
}
mCachedImageFetcher.fetchImage(
url, CachedImageFetcher.CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME, callback);
mImageFetcher.fetchImage(
url, ImageFetcher.CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME, callback);
}
@Override
......@@ -70,9 +75,8 @@ class ContextualSuggestionsSourceImpl
return;
}
mCachedImageFetcher.fetchImage(url,
CachedImageFetcher.CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME, desiredSizePx,
desiredSizePx, callback);
mImageFetcher.fetchImage(url, ImageFetcher.CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME,
desiredSizePx, desiredSizePx, callback);
}
@Override
......@@ -91,9 +95,8 @@ class ContextualSuggestionsSourceImpl
}
@VisibleForTesting
ContextualSuggestionsSourceImpl(
ContextualSuggestionsBridge bridge, CachedImageFetcher cachedImageFethcer) {
ContextualSuggestionsSourceImpl(ContextualSuggestionsBridge bridge, ImageFetcher imageFetcher) {
mBridge = bridge;
mCachedImageFetcher = cachedImageFethcer;
mImageFetcher = imageFetcher;
}
}
......@@ -5,76 +5,149 @@
package org.chromium.chrome.browser.image_fetcher;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.chromium.base.Callback;
import org.chromium.base.ThreadUtils;
import org.chromium.base.Log;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.task.AsyncTask;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import jp.tomorrowkey.android.gifplayer.BaseGifImage;
/**
* Provides cached image fetching capabilities. Uses getLastUsedProfile, which
* will need to be changed when supporting multi-profile.
* ImageFetcher implementation that uses a disk cache.
*/
public interface CachedImageFetcher {
// All UMA client names collected here to prevent duplicates.
public static final String ASSISTANT_DETAILS_UMA_CLIENT_NAME = "AssistantDetails";
public static final String ASSISTANT_INFO_BOX_UMA_CLIENT_NAME = "AssistantInfoBox";
public static final String CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME = "ContextualSuggestions";
public static final String FEED_UMA_CLIENT_NAME = "Feed";
public static final String NTP_ANIMATED_LOGO_UMA_CLIENT_NAME = "NewTabPageAnimatedLogo";
static CachedImageFetcher getInstance() {
ThreadUtils.assertOnUiThread();
return CachedImageFetcherImpl.getInstance();
}
public class CachedImageFetcher extends ImageFetcher {
private static final String TAG = "CachedImageFetcher";
/**
* Report an event metric.
*
* @param clientName Name of the cached image fetcher client to report UMA metrics for.
* @param eventId The event to be reported
*/
void reportEvent(String clientName, @CachedImageFetcherEvent int eventId);
// The native bridge.
private ImageFetcherBridge mImageFetcherBridge;
/**
* Fetch the gif for the given url.
* Creates a CachedImageFetcher with the given bridge.
*
* @param url The url to fetch the image from.
* @param clientName The UMA client name to report the metrics to. If using CachedImageFetcher
* to fetch images and gifs, use separate clientNames for them.
* @param callback The function which will be called when the image is ready; will be called
* with null result if fetching fails.
* @param bridge Bridge used to interact with native.
*/
void fetchGif(String url, String clientName, Callback<BaseGifImage> callback);
protected CachedImageFetcher(ImageFetcherBridge bridge) {
mImageFetcherBridge = bridge;
}
/**
* Fetches the image at url with the desired size. Image is null if not
* found or fails decoding.
*
* @param url The url to fetch the image from.
* @param clientName Name of the cached image fetcher client to report UMA metrics for.
* @param width The new bitmap's desired width (in pixels). If the given value is <= 0, the
* image won't be scaled.
* @param height The new bitmap's desired height (in pixels). If the given value is <= 0, the
* image won't be scaled.
* @param callback The function which will be called when the image is ready; will be called
* with null result if fetching fails;
*/
void fetchImage(
String url, String clientName, int width, int height, Callback<Bitmap> callback);
@Override
public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
mImageFetcherBridge.reportEvent(clientName, eventId);
}
@Override
public void destroy() {
// Do nothing, this lives for the lifetime of the application.
}
@Override
public void fetchGif(String url, String clientName, Callback<BaseGifImage> callback) {
long startTimeMillis = System.currentTimeMillis();
String filePath = mImageFetcherBridge.getFilePath(url);
// TODO(crbug.com/947176): Use the new PostTask api for deferred tasks.
new AsyncTask<BaseGifImage>() {
@Override
protected BaseGifImage doInBackground() {
return tryToLoadGifFromDisk(filePath);
}
@Override
protected void onPostExecute(BaseGifImage gif) {
if (gif != null) {
callback.onResult(gif);
reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
mImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
} else {
mImageFetcherBridge.fetchGif(url, clientName, (BaseGifImage gifFromNative) -> {
callback.onResult(gifFromNative);
mImageFetcherBridge.reportTotalFetchTimeFromNative(
clientName, startTimeMillis);
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void fetchImage(
String url, String clientName, int width, int height, Callback<Bitmap> callback) {
fetchImageImpl(url, clientName, width, height, callback);
}
@Override
public @ImageFetcherConfig int getConfig() {
return ImageFetcherConfig.DISK_CACHE_ONLY;
}
/**
* Alias of fetchImage that ignores scaling.
* Starts an AsyncTask to first check the disk for the desired image, then fetches from the
* network if it isn't found.
*
* @param url The url to fetch the image from.
* @param clientName Name of the cached image fetcher client to report UMA metrics for.
* @param callback The function which will be called when the image is ready; will be called
* with null result if fetching fails;
* @param width The new bitmap's desired width (in pixels).
* @param height The new bitmap's desired height (in pixels).
* @param callback The function which will be called when the image is ready.
*/
void fetchImage(String url, String clientName, Callback<Bitmap> callback);
@VisibleForTesting
void fetchImageImpl(
String url, String clientName, int width, int height, Callback<Bitmap> callback) {
long startTimeMillis = System.currentTimeMillis();
String filePath = mImageFetcherBridge.getFilePath(url);
// TODO(crbug.com/947176): Use the new PostTask api for deferred tasks.
new AsyncTask<Bitmap>() {
@Override
protected Bitmap doInBackground() {
return tryToLoadImageFromDisk(filePath);
}
/**
* Destroy method, called to clear resources to prevent leakage.
*/
void destroy();
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
callback.onResult(bitmap);
reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
mImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
} else {
mImageFetcherBridge.fetchImage(getConfig(), url, clientName, width, height,
(Bitmap bitmapFromNative) -> {
callback.onResult(bitmapFromNative);
mImageFetcherBridge.reportTotalFetchTimeFromNative(
clientName, startTimeMillis);
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/** Wrapper function to decode a file for disk, useful for testing. */
@VisibleForTesting
Bitmap tryToLoadImageFromDisk(String filePath) {
if (new File(filePath).exists()) {
return BitmapFactory.decodeFile(filePath, null);
} else {
return null;
}
}
@VisibleForTesting
BaseGifImage tryToLoadGifFromDisk(String filePath) {
try {
File file = new File(filePath);
byte[] fileBytes = new byte[(int) file.length()];
FileInputStream fileInputStream = new FileInputStream(filePath);
int bytesRead = fileInputStream.read(fileBytes);
if (bytesRead != fileBytes.length) return null;
return new BaseGifImage(fileBytes);
} catch (IOException e) {
Log.w(TAG, "Failed to read: %s", filePath, e);
return null;
}
}
}
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.image_fetcher;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import org.chromium.base.Callback;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.task.AsyncTask;
import org.chromium.chrome.browser.profiles.Profile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import jp.tomorrowkey.android.gifplayer.BaseGifImage;
/**
* Implementation that uses a disk cache.
*/
public class CachedImageFetcherImpl implements CachedImageFetcher {
private static final String TAG = "CachedImageFetcher";
private static CachedImageFetcherImpl sInstance;
public static CachedImageFetcherImpl getInstance() {
ThreadUtils.assertOnUiThread();
if (sInstance == null) {
sInstance = new CachedImageFetcherImpl(Profile.getLastUsedProfile());
}
return sInstance;
}
// The native bridge.
private ImageFetcherBridge mCachedImageFetcherBridge;
/**
* Creates a CachedImageFetcher for the current user.
*
* @param profile Profile of the user we are fetching for.
*/
CachedImageFetcherImpl(Profile profile) {
this(new ImageFetcherBridge(profile));
}
/**
* Creates a CachedImageFetcher for testing.
*
* @param bridge Mock bridge to use.
*/
@VisibleForTesting
CachedImageFetcherImpl(ImageFetcherBridge bridge) {
mCachedImageFetcherBridge = bridge;
}
@Override
public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
mCachedImageFetcherBridge.reportEvent(clientName, eventId);
}
@Override
public void destroy() {
// Do nothing, this lives for the lifetime of the application.
}
@Override
public void fetchGif(String url, String clientName, Callback<BaseGifImage> callback) {
long startTimeMillis = System.currentTimeMillis();
String filePath = mCachedImageFetcherBridge.getFilePath(url);
new AsyncTask<BaseGifImage>() {
@Override
protected BaseGifImage doInBackground() {
return tryToLoadGifFromDisk(filePath);
}
@Override
protected void onPostExecute(BaseGifImage gif) {
if (gif != null) {
callback.onResult(gif);
reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
mCachedImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
} else {
mCachedImageFetcherBridge.fetchGif(
url, clientName, (BaseGifImage gifFromNative) -> {
callback.onResult(gifFromNative);
mCachedImageFetcherBridge.reportTotalFetchTimeFromNative(
clientName, startTimeMillis);
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void fetchImage(
String url, String clientName, int width, int height, Callback<Bitmap> callback) {
fetchImageImpl(url, clientName, width, height, callback);
}
@Override
public void fetchImage(String url, String clientName, Callback<Bitmap> callback) {
fetchImageImpl(url, clientName, 0, 0, callback);
}
/**
* Starts an AsyncTask to first check the disk for the desired image, then
* fetches from the network if it isn't found.
*
* @param url The url to fetch the image from.
* @param width The new bitmap's desired width (in pixels).
* @param height The new bitmap's desired height (in pixels).
* @param callback The function which will be called when the image is ready.
*/
@VisibleForTesting
void fetchImageImpl(
String url, String clientName, int width, int height, Callback<Bitmap> callback) {
long startTimeMillis = System.currentTimeMillis();
String filePath = mCachedImageFetcherBridge.getFilePath(url);
// TODO(wylieb): Transition to new way of doing async tasks in this file.
new AsyncTask<Bitmap>() {
@Override
protected Bitmap doInBackground() {
return tryToLoadImageFromDisk(filePath);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (bitmap != null) {
callback.onResult(bitmap);
reportEvent(clientName, CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT);
mCachedImageFetcherBridge.reportCacheHitTime(clientName, startTimeMillis);
} else {
mCachedImageFetcherBridge.fetchImage(ImageFetcherConfig.DISK_CACHE_ONLY, url,
clientName, 0, 0, (Bitmap bitmapFromNative) -> {
callback.onResult(bitmapFromNative);
mCachedImageFetcherBridge.reportTotalFetchTimeFromNative(
clientName, startTimeMillis);
});
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
/** Wrapper function to decode a file for disk, useful for testing. */
@VisibleForTesting
Bitmap tryToLoadImageFromDisk(String filePath) {
if (new File(filePath).exists()) {
return BitmapFactory.decodeFile(filePath, null);
} else {
return null;
}
}
@VisibleForTesting
BaseGifImage tryToLoadGifFromDisk(String filePath) {
try {
File file = new File(filePath);
byte[] fileBytes = new byte[(int) file.length()];
FileInputStream ios = new FileInputStream(filePath);
int bytesRead = ios.read(fileBytes);
if (bytesRead != fileBytes.length) return null;
return new BaseGifImage(fileBytes);
} catch (IOException e) {
Log.w(TAG, "Failed to read: %s", filePath, e);
return null;
}
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.image_fetcher;
import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
import android.support.annotation.Nullable;
import org.chromium.base.Callback;
import org.chromium.base.VisibleForTesting;
import jp.tomorrowkey.android.gifplayer.BaseGifImage;
/**
* Blueprint and some implementation for image fetching. Use ImageFetcherFactory for any
* ImageFetcher instantiation.
*/
public abstract class ImageFetcher {
// All UMA client names collected here to prevent duplicates.
public static final String ASSISTANT_DETAILS_UMA_CLIENT_NAME = "AssistantDetails";
public static final String ASSISTANT_INFO_BOX_UMA_CLIENT_NAME = "AssistantInfoBox";
public static final String CONTEXTUAL_SUGGESTIONS_UMA_CLIENT_NAME = "ContextualSuggestions";
public static final String FEED_UMA_CLIENT_NAME = "Feed";
public static final String NTP_ANIMATED_LOGO_UMA_CLIENT_NAME = "NewTabPageAnimatedLogo";
/**
* Try to resize the given image if the conditions are met.
*
* @param bitmap The input bitmap, will be recycled if scaled.
* @param width The desired width of the output.
* @param height The desired height of the output.
*
* @return The resized image, or the original image if the conditions aren't met.
*/
@VisibleForTesting
public static Bitmap tryToResizeImage(@Nullable Bitmap bitmap, int width, int height) {
if (bitmap != null && width > 0 && height > 0 && bitmap.getWidth() != width
&& bitmap.getHeight() != height) {
/* The resizing rules are the as follows:
(1) The image will be scaled up (if smaller) in a way that maximizes the area of the
source bitmap that's in the destination bitmap.
(2) A crop is made in the middle of the bitmap for the given size (width, height).
The x/y are placed appropriately (conceptually just think of it as a properly sized
chunk taken from the middle). */
return ThumbnailUtils.extractThumbnail(
bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
} else {
return bitmap;
}
}
/**
* Report an event metric.
*
* @param clientName Name of the cached image fetcher client to report UMA metrics for.
* @param eventId The event to be reported
*/
// TODO(crbug.com/947210): Rename this enum to ImageFetcherEvent.
// TOOD(crbug.com/947220): Implement this function here, and remove it from derived classes.
public abstract void reportEvent(String clientName, @CachedImageFetcherEvent int eventId);
/**
* Fetch the gif for the given url.
*
* @param url The url to fetch the image from.
* @param clientName The UMA client name to report the metrics to. If using CachedImageFetcher
* to fetch images and gifs, use separate clientNames for them.
* @param callback The function which will be called when the image is ready; will be called
* with null result if fetching fails.
*/
public abstract void fetchGif(String url, String clientName, Callback<BaseGifImage> callback);
/**
* Fetches the image at url with the desired size. Image is null if not found or fails decoding.
*
* @param url The url to fetch the image from.
* @param clientName Name of the cached image fetcher client to report UMA metrics for.
* @param width The new bitmap's desired width (in pixels). If the given value is <= 0, the
* image won't be scaled.
* @param height The new bitmap's desired height (in pixels). If the given value is <= 0, the
* image won't be scaled.
* @param callback The function which will be called when the image is ready; will be called
* with null result if fetching fails;
*/
public abstract void fetchImage(
String url, String clientName, int width, int height, Callback<Bitmap> callback);
/**
* Alias of fetchImage that ignores scaling.
*
* @param url The url to fetch the image from.
* @param clientName Name of the cached image fetcher client to report UMA metrics for.
* @param callback The function which will be called when the image is ready; will be called
* with null result if fetching fails;
*/
public void fetchImage(String url, String clientName, Callback<Bitmap> callback) {
fetchImage(url, clientName, 0, 0, callback);
}
/**
* Returns the type of Image Fetcher this is based on class arrangements. See
* image_fetcher_service.h for a detailed description of the available configurations.
*
* @return the type of the image fetcher this class maps to in native.
*/
public abstract @ImageFetcherConfig int getConfig();
/**
* Destroy method, called to clear resources to prevent leakage.
*/
public abstract void destroy();
}
......@@ -90,7 +90,9 @@ public class ImageFetcherBridge {
public void fetchImage(@ImageFetcherConfig int config, String url, String clientName, int width,
int height, Callback<Bitmap> callback) {
assert mNativeImageFetcherBridge != 0;
nativeFetchImage(mNativeImageFetcherBridge, config, url, clientName, callback);
nativeFetchImage(mNativeImageFetcherBridge, config, url, clientName, (bitmap) -> {
callback.onResult(ImageFetcher.tryToResizeImage(bitmap, width, height));
});
}
/**
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.image_fetcher;
import org.chromium.base.DiscardableReferencePool;
/**
* Factory to provide the image fetcher best suited for the given config.
*/
public class ImageFetcherFactory {
// Store static references to singleton image fetchers.
private static CachedImageFetcher sCachedImageFetcher;
/**
* Alias for createImageFetcher below.
*
* @param config The type of ImageFetcher you need.
* @return The correct ImageFetcher according to the provided config.
*/
public static ImageFetcher createImageFetcher(@ImageFetcherConfig int config) {
return createImageFetcher(config, null);
}
/**
* Alias for createImageFetcher below.
*
* @param config The type of ImageFetcher you need.
* @param discardableReferencePool Used to store images in-memory.
* @return The correct ImageFetcher according to the provided config.
*/
public static ImageFetcher createImageFetcher(
@ImageFetcherConfig int config, DiscardableReferencePool discardableReferencePool) {
return createImageFetcher(
config, discardableReferencePool, ImageFetcherBridge.getInstance());
}
/**
* Return the image fetcher that matches the given config. This will return an image fetcher
* config that you must destroy.
*
* @param config The type of ImageFetcher you need.
* @param discardableReferencePool Used to store images in-memory.
* @param imageFetcherBridge Bridge to use.
* @return The correct ImageFetcher according to the provided config.
*/
public static ImageFetcher createImageFetcher(@ImageFetcherConfig int config,
DiscardableReferencePool discardableReferencePool,
ImageFetcherBridge imageFetcherBridge) {
// TODO(crbug.com/947191):Allow server-side configuration image fetcher clients.
switch (config) {
case ImageFetcherConfig.NETWORK_ONLY:
// TODO(crbug.com/944517): Implement a network image fetcher.
return null;
case ImageFetcherConfig.DISK_CACHE_ONLY:
if (sCachedImageFetcher == null) {
sCachedImageFetcher = new CachedImageFetcher(imageFetcherBridge);
}
return sCachedImageFetcher;
case ImageFetcherConfig.IN_MEMORY_ONLY:
// TODO(crbug.com/944517): Return this once NetworkImageFetcher is available.
return null;
case ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE:
assert discardableReferencePool != null;
return new InMemoryCachedImageFetcher(
createImageFetcher(
ImageFetcherConfig.DISK_CACHE_ONLY, null, imageFetcherBridge),
discardableReferencePool);
default:
return null;
}
}
}
......@@ -5,7 +5,6 @@
package org.chromium.chrome.browser.image_fetcher;
import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
import android.support.annotation.Nullable;
import org.chromium.base.Callback;
......@@ -17,23 +16,25 @@ import org.chromium.chrome.browser.util.ConversionUtils;
import jp.tomorrowkey.android.gifplayer.BaseGifImage;
/**
* A wrapper around the CachedImageFetcher that also provides in-memory caching.
* ImageFetcher implementation with an in-memory cache. Can also be configured to use a disk cache.
*/
public class InMemoryCachedImageFetcher implements CachedImageFetcher {
public class InMemoryCachedImageFetcher extends ImageFetcher {
private static final int DEFAULT_CACHE_SIZE = 20 * ConversionUtils.BYTES_PER_MEGABYTE; // 20mb
private static final float PORTION_OF_AVAILABLE_MEMORY = 1.f / 8.f;
// Will do the work if the image isn't cached in memory.
private CachedImageFetcher mCachedImageFetcher;
private ImageFetcher mImageFetcher;
private BitmapCache mBitmapCache;
private @ImageFetcherConfig int mConfig;
/**
* Create an instance with the default max cache size.
*
* @param imageFetcher The image fetcher to back this. Must be Cached/NetworkImageFetcher.
* @param referencePool Pool used to discard references when under memory pressure.
*/
public InMemoryCachedImageFetcher(DiscardableReferencePool referencePool) {
this(referencePool, DEFAULT_CACHE_SIZE);
InMemoryCachedImageFetcher(ImageFetcher imageFetcher, DiscardableReferencePool referencePool) {
init(imageFetcher, new BitmapCache(referencePool, determineCacheSize(DEFAULT_CACHE_SIZE)));
}
/**
......@@ -43,29 +44,54 @@ public class InMemoryCachedImageFetcher implements CachedImageFetcher {
* @param cacheSize The cache size to use (in bytes), may be smaller depending on the device's
* memory.
*/
public InMemoryCachedImageFetcher(DiscardableReferencePool referencePool, int cacheSize) {
int actualCacheSize = determineCacheSize(cacheSize);
mBitmapCache = new BitmapCache(referencePool, actualCacheSize);
mCachedImageFetcher = CachedImageFetcher.getInstance();
InMemoryCachedImageFetcher(
ImageFetcher imageFetcher, DiscardableReferencePool referencePool, int cacheSize) {
init(imageFetcher, new BitmapCache(referencePool, determineCacheSize(cacheSize)));
}
@Override
public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
mCachedImageFetcher.reportEvent(clientName, eventId);
/**
* @param referencePool Pool used to discard references when under memory pressure.
* @param bitmapCache The cached where bitmaps will be stored in memory.
* memory.
*/
private void init(ImageFetcher imageFetcher, BitmapCache bitmapCache) {
mBitmapCache = bitmapCache;
mImageFetcher = imageFetcher;
@ImageFetcherConfig
int underlyingConfig = mImageFetcher.getConfig();
assert (underlyingConfig == ImageFetcherConfig.NETWORK_ONLY
|| underlyingConfig == ImageFetcherConfig.DISK_CACHE_ONLY)
: "Invalid underlying config for InMemoryCachedImageFetcher";
// Determine the config based on the composited image fetcher.
if (mImageFetcher.getConfig() == ImageFetcherConfig.NETWORK_ONLY) {
mConfig = ImageFetcherConfig.IN_MEMORY_ONLY;
} else if (mImageFetcher.getConfig() == ImageFetcherConfig.DISK_CACHE_ONLY) {
mConfig = ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE;
} else {
// Report in memory only if none can be found.
mConfig = ImageFetcherConfig.IN_MEMORY_ONLY;
}
}
@Override
public void destroy() {
mCachedImageFetcher.destroy();
mCachedImageFetcher = null;
mImageFetcher.destroy();
mImageFetcher = null;
mBitmapCache.destroy();
mBitmapCache = null;
}
@Override
public void reportEvent(String clientName, @CachedImageFetcherEvent int eventId) {
mImageFetcher.reportEvent(clientName, eventId);
}
@Override
public void fetchGif(String url, String clientName, Callback<BaseGifImage> callback) {
mCachedImageFetcher.fetchGif(url, clientName, callback);
mImageFetcher.fetchGif(url, clientName, callback);
}
@Override
......@@ -73,17 +99,16 @@ public class InMemoryCachedImageFetcher implements CachedImageFetcher {
String url, String clientName, int width, int height, Callback<Bitmap> callback) {
Bitmap cachedBitmap = tryToGetBitmap(url, width, height);
if (cachedBitmap == null) {
if (mCachedImageFetcher == null) {
// This will be run if destroy() has been called.
if (mImageFetcher == null) {
callback.onResult(null);
return;
}
mCachedImageFetcher.fetchImage(
url, clientName, width, height, (@Nullable Bitmap bitmap) -> {
bitmap = tryToResizeImage(bitmap, width, height);
storeBitmap(bitmap, url, width, height);
callback.onResult(bitmap);
});
mImageFetcher.fetchImage(url, clientName, width, height, (@Nullable Bitmap bitmap) -> {
storeBitmap(bitmap, url, width, height);
callback.onResult(bitmap);
});
} else {
reportEvent(clientName, CachedImageFetcherEvent.JAVA_IN_MEMORY_CACHE_HIT);
callback.onResult(cachedBitmap);
......@@ -91,12 +116,12 @@ public class InMemoryCachedImageFetcher implements CachedImageFetcher {
}
@Override
public void fetchImage(String url, String clientName, Callback<Bitmap> callback) {
fetchImage(url, clientName, 0, 0, callback);
public @ImageFetcherConfig int getConfig() {
return mConfig;
}
/**
* Try to get a bitmap from the in-memory cache.
* Try to get a bitmap from the in-memory cache. Returns null if this object has been destroyed.
*
* @param url The url of the image.
* @param width The width (in pixels) of the image.
......@@ -159,36 +184,10 @@ public class InMemoryCachedImageFetcher implements CachedImageFetcher {
return Math.min(maxCacheUsage, preferredCacheSize);
}
/**
* Try to resize the given image if the conditions are met.
*
* @param bitmap The input bitmap, will be recycled if scaled.
* @param width The desired width of the output.
* @param height The desired height of the output.
*
* @return The resized image, or the original image if the conditions aren't met.
*/
@VisibleForTesting
Bitmap tryToResizeImage(@Nullable Bitmap bitmap, int width, int height) {
if (bitmap != null && width > 0 && height > 0 && bitmap.getWidth() != width
&& bitmap.getHeight() != height) {
/* The resizing rules are the as follows:
(1) The image will be scaled up (if smaller) in a way that maximizes the area of the
source bitmap that's in the destination bitmap.
(2) A crop is made in the middle of the bitmap for the given size (width, height).
The x/y are placed appropriately (conceptually just think of it as a properly sized
chunk taken from the middle). */
return ThumbnailUtils.extractThumbnail(
bitmap, width, height, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
} else {
return bitmap;
}
}
/** Test constructor. */
@VisibleForTesting
InMemoryCachedImageFetcher(BitmapCache bitmapCache, CachedImageFetcher cachedImageFetcher) {
InMemoryCachedImageFetcher(BitmapCache bitmapCache, ImageFetcher imageFetcher) {
mBitmapCache = bitmapCache;
mCachedImageFetcher = cachedImageFetcher;
mImageFetcher = imageFetcher;
}
}
\ No newline at end of file
}
......@@ -5,7 +5,9 @@
package org.chromium.chrome.browser.ntp;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcher;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherConfig;
import org.chromium.chrome.browser.image_fetcher.ImageFetcherFactory;
import org.chromium.chrome.browser.ntp.LogoBridge.Logo;
import org.chromium.chrome.browser.ntp.LogoBridge.LogoObserver;
import org.chromium.chrome.browser.profiles.Profile;
......@@ -39,11 +41,12 @@ public class LogoDelegateImpl implements LogoView.Delegate {
private LogoView mLogoView;
private LogoBridge mLogoBridge;
private ImageFetcher mImageFetcher;
private String mOnLogoClickUrl;
private String mAnimatedLogoUrl;
private boolean mShouldRecordLoadTime = true;
private boolean mIsDestroyed;
/**
......@@ -57,10 +60,13 @@ public class LogoDelegateImpl implements LogoView.Delegate {
mNavigationDelegate = navigationDelegate;
mLogoView = logoView;
mLogoBridge = new LogoBridge(profile);
mImageFetcher = ImageFetcherFactory.createImageFetcher(ImageFetcherConfig.DISK_CACHE_ONLY);
}
public void destroy() {
mIsDestroyed = true;
mImageFetcher.destroy();
mImageFetcher = null;
}
@Override
......@@ -70,8 +76,7 @@ public class LogoDelegateImpl implements LogoView.Delegate {
if (!isAnimatedLogoShowing && mAnimatedLogoUrl != null) {
RecordHistogram.recordSparseHistogram(LOGO_CLICK_UMA_NAME, CTA_IMAGE_CLICKED);
mLogoView.showLoadingView();
CachedImageFetcher.getInstance().fetchGif(mAnimatedLogoUrl,
CachedImageFetcher.NTP_ANIMATED_LOGO_UMA_CLIENT_NAME,
mImageFetcher.fetchGif(mAnimatedLogoUrl, ImageFetcher.NTP_ANIMATED_LOGO_UMA_CLIENT_NAME,
(BaseGifImage animatedLogoImage) -> {
if (mIsDestroyed || animatedLogoImage == null) return;
mLogoView.playAnimatedLogo(animatedLogoImage);
......
......@@ -32,7 +32,7 @@ import org.chromium.chrome.browser.image_fetcher.CachedImageFetcher;
import org.chromium.chrome.browser.ntp.snippets.SnippetArticle;
/**
* Unit tests for CachedImageFetcherImpl.
* Unit tests for ContextualSuggestionsSourceImplTest.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
......
......@@ -37,21 +37,21 @@ import org.chromium.chrome.browser.contextmenu.ChromeContextMenuPopulatorTest.Sh
import jp.tomorrowkey.android.gifplayer.BaseGifImage;
/**
* Unit tests for CachedImageFetcherImpl.
* Unit tests for CachedImageFetcher.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows = {ShadowUrlUtilities.class, BackgroundShadowAsyncTask.class})
public class CachedImageFetcherImplTest {
public class CachedImageFetcherTest {
private static final String UMA_CLIENT_NAME = "TestUmaClient";
private static final String URL = "http://foo.bar";
private static final int WIDTH_PX = 100;
private static final int HEIGHT_PX = 200;
CachedImageFetcherImpl mCachedImageFetcher;
CachedImageFetcher mCachedImageFetcher;
@Mock
ImageFetcherBridge mCachedImageFetcherBridge;
ImageFetcherBridge mImageFetcherBridge;
@Mock
Bitmap mBitmap;
@Mock
......@@ -63,14 +63,14 @@ public class CachedImageFetcherImplTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mCachedImageFetcher = Mockito.spy(new CachedImageFetcherImpl(mCachedImageFetcherBridge));
Mockito.doReturn(URL).when(mCachedImageFetcherBridge).getFilePath(anyObject());
mCachedImageFetcher = Mockito.spy(new CachedImageFetcher(mImageFetcherBridge));
Mockito.doReturn(URL).when(mImageFetcherBridge).getFilePath(anyObject());
doAnswer((InvocationOnMock invocation) -> {
mCallbackCaptor.getValue().onResult(mBitmap);
return null;
})
.when(mCachedImageFetcherBridge)
.fetchImage(eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(),
.when(mImageFetcherBridge)
.fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(),
mCallbackCaptor.capture());
}
......@@ -85,14 +85,13 @@ public class CachedImageFetcherImplTest {
verify(mCachedImageFetcher)
.fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
verify(mCachedImageFetcherBridge, never()) // Should never make it to native.
.fetchImage(
eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
verify(mImageFetcherBridge, never()) // Should never make it to native.
.fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
// Verify metrics have been reported.
verify(mCachedImageFetcherBridge)
verify(mImageFetcherBridge)
.reportEvent(eq(UMA_CLIENT_NAME), eq(CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT));
verify(mCachedImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), anyLong());
verify(mImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), anyLong());
}
@Test
......@@ -106,9 +105,8 @@ public class CachedImageFetcherImplTest {
verify(mCachedImageFetcher)
.fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
verify(mCachedImageFetcherBridge)
.fetchImage(
eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
verify(mImageFetcherBridge)
.fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
}
@Test
......@@ -122,9 +120,8 @@ public class CachedImageFetcherImplTest {
verify(mCachedImageFetcher)
.fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
verify(mCachedImageFetcherBridge, never())
.fetchImage(
eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
verify(mImageFetcherBridge, never())
.fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
}
@Test
......@@ -138,9 +135,8 @@ public class CachedImageFetcherImplTest {
verify(mCachedImageFetcher)
.fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
verify(mCachedImageFetcherBridge)
.fetchImage(
eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
verify(mImageFetcherBridge)
.fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
}
@Test
......@@ -158,12 +154,11 @@ public class CachedImageFetcherImplTest {
.fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME), eq(0), eq(0), any());
verify(mCachedImageFetcher)
.fetchImageImpl(eq(URL), eq(UMA_CLIENT_NAME + "2"), eq(0), eq(0), any());
verify(mCachedImageFetcherBridge)
verify(mImageFetcherBridge)
.fetchImage(anyInt(), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
verify(mImageFetcherBridge)
.fetchImage(
eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME), anyInt(), anyInt(), any());
verify(mCachedImageFetcherBridge)
.fetchImage(eq(getConfig()), eq(URL), eq(UMA_CLIENT_NAME + "2"), anyInt(), anyInt(),
any());
anyInt(), eq(URL), eq(UMA_CLIENT_NAME + "2"), anyInt(), anyInt(), any());
}
@Test
......@@ -175,13 +170,13 @@ public class CachedImageFetcherImplTest {
BackgroundShadowAsyncTask.runBackgroundTasks();
ShadowLooper.runUiThreadTasks();
verify(mCachedImageFetcherBridge, never()) // Should never make it to native.
verify(mImageFetcherBridge, never()) // Should never make it to native.
.fetchGif(eq(URL), eq(UMA_CLIENT_NAME), any());
// Verify metrics have been reported.
verify(mCachedImageFetcherBridge)
verify(mImageFetcherBridge)
.reportEvent(eq(UMA_CLIENT_NAME), eq(CachedImageFetcherEvent.JAVA_DISK_CACHE_HIT));
verify(mCachedImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), anyLong());
verify(mImageFetcherBridge).reportCacheHitTime(eq(UMA_CLIENT_NAME), anyLong());
}
@Test
......@@ -192,10 +187,6 @@ public class CachedImageFetcherImplTest {
BackgroundShadowAsyncTask.runBackgroundTasks();
ShadowLooper.runUiThreadTasks();
verify(mCachedImageFetcherBridge).fetchGif(eq(URL), eq(UMA_CLIENT_NAME), any());
}
private @ImageFetcherConfig int getConfig() {
return ImageFetcherConfig.DISK_CACHE_ONLY;
verify(mImageFetcherBridge).fetchGif(eq(URL), eq(UMA_CLIENT_NAME), any());
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.image_fetcher;
import static org.junit.Assert.assertEquals;
import android.support.test.filters.SmallTest;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.DiscardableReferencePool;
import org.chromium.base.test.BaseRobolectricTestRunner;
/**
* Test for ImageFetcherFactory.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ImageFetcherFactoryTest {
@Mock
ImageFetcherBridge mImageFetcherBridge;
@Mock
DiscardableReferencePool mReferencePool;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
@SmallTest
public void testcreateImageFetcher() {
// TODO(crbug.com/944517): Update these tests once NetworkImageFetcher is added.
assertEquals(null,
ImageFetcherFactory.createImageFetcher(
ImageFetcherConfig.NETWORK_ONLY, mReferencePool, mImageFetcherBridge));
assertEquals(ImageFetcherConfig.DISK_CACHE_ONLY,
ImageFetcherFactory
.createImageFetcher(ImageFetcherConfig.DISK_CACHE_ONLY, mReferencePool,
mImageFetcherBridge)
.getConfig());
assertEquals(null,
ImageFetcherFactory.createImageFetcher(
ImageFetcherConfig.IN_MEMORY_ONLY, mReferencePool, mImageFetcherBridge));
assertEquals(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
ImageFetcherFactory
.createImageFetcher(ImageFetcherConfig.IN_MEMORY_WITH_DISK_CACHE,
mReferencePool, mImageFetcherBridge)
.getConfig());
}
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.image_fetcher;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import android.graphics.Bitmap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.chromium.base.Callback;
import org.chromium.base.test.BaseRobolectricTestRunner;
import jp.tomorrowkey.android.gifplayer.BaseGifImage;
/**
* Test for ImageFetcher.java.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ImageFetcherTest {
private static final int WIDTH_PX = 100;
private static final int HEIGHT_PX = 200;
/**
* Concrete implementation for testing purposes.
*/
private class ImageFetcherForTest extends ImageFetcher {
@Override
public void reportEvent(String clientName, int eventId) {}
@Override
public void fetchGif(String url, String clientName, Callback<BaseGifImage> callback) {}
@Override
public void fetchImage(
String url, String clientName, int width, int height, Callback<Bitmap> callback) {}
@Override
public int getConfig() {
return 0;
}
@Override
public void destroy() {}
}
private final Bitmap mBitmap =
Bitmap.createBitmap(WIDTH_PX, HEIGHT_PX, Bitmap.Config.ARGB_8888);
private ImageFetcherForTest mImageFetcher;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mImageFetcher = Mockito.spy(new ImageFetcherForTest());
}
@Test
public void testResize() throws Exception {
Bitmap result = ImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX / 2, HEIGHT_PX / 2);
assertNotEquals(result, mBitmap);
}
@Test
public void testResizeBailsOutIfSizeIsZeroOrLess() throws Exception {
Bitmap result = ImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX - 1, HEIGHT_PX - 1);
assertNotEquals(result, mBitmap);
result = ImageFetcher.tryToResizeImage(mBitmap, 0, HEIGHT_PX);
assertEquals(result, mBitmap);
result = ImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX, 0);
assertEquals(result, mBitmap);
result = ImageFetcher.tryToResizeImage(mBitmap, 0, 0);
assertEquals(result, mBitmap);
result = ImageFetcher.tryToResizeImage(mBitmap, -1, HEIGHT_PX);
assertEquals(result, mBitmap);
result = ImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX, -1);
assertEquals(result, mBitmap);
}
@Test
public void testFetchImageNoDimensionsAlias() {
String url = "url";
String client = "client";
mImageFetcher.fetchImage(url, client, result -> {});
// No arguments should alias to 0, 0.
verify(mImageFetcher).fetchImage(eq(url), eq(client), eq(0), eq(0), any());
}
}
......@@ -4,8 +4,6 @@
package org.chromium.chrome.browser.image_fetcher;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
......@@ -55,7 +53,7 @@ public class InMemoryCachedImageFetcherTest {
private DiscardableReferencePool mReferencePool;
@Mock
private CachedImageFetcherImpl mCachedImageFetcherImpl;
private CachedImageFetcher mCachedImageFetcher;
@Mock
private Callback<Bitmap> mCallback;
......@@ -72,7 +70,7 @@ public class InMemoryCachedImageFetcherTest {
mReferencePool = new DiscardableReferencePool();
mBitmapCache = new BitmapCache(mReferencePool, DEFAULT_CACHE_SIZE);
mInMemoryCachedImageFetcher =
spy(new InMemoryCachedImageFetcher(mBitmapCache, mCachedImageFetcherImpl));
spy(new InMemoryCachedImageFetcher(mBitmapCache, mCachedImageFetcher));
}
public void answerFetch(Bitmap bitmap, CachedImageFetcher cachedImageFetcher,
......@@ -88,20 +86,16 @@ public class InMemoryCachedImageFetcherTest {
mCallbackCaptor.getValue().onResult(bitmap);
return null;
}).when(mCachedImageFetcherImpl)
}).when(mCachedImageFetcher)
.fetchImage(eq(URL), eq(UMA_CLIENT_NAME), mWidthCaptor.capture(),
mHeightCaptor.capture(), mCallbackCaptor.capture());
// clang-format on
doReturn(bitmap)
.when(mInMemoryCachedImageFetcher)
.tryToResizeImage(eq(bitmap), eq(WIDTH_PX), eq(HEIGHT_PX));
}
@Test
@SmallTest
public void testFetchImageCachesFirstCall() throws Exception {
answerFetch(mBitmap, mCachedImageFetcherImpl, false);
answerFetch(mBitmap, mCachedImageFetcher, false);
mInMemoryCachedImageFetcher.fetchImage(
URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, mCallback);
verify(mCallback).onResult(eq(mBitmap));
......@@ -111,11 +105,11 @@ public class InMemoryCachedImageFetcherTest {
URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, mCallback);
verify(mCallback).onResult(eq(mBitmap));
verify(mCachedImageFetcherImpl, /* Should only go to native the first time. */ times(1))
verify(mCachedImageFetcher, /* Should only go to native the first time. */ times(1))
.fetchImage(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
// Verify metrics are reported.
verify(mCachedImageFetcherImpl)
verify(mCachedImageFetcher)
.reportEvent(
eq(UMA_CLIENT_NAME), eq(CachedImageFetcherEvent.JAVA_IN_MEMORY_CACHE_HIT));
}
......@@ -132,49 +126,15 @@ public class InMemoryCachedImageFetcherTest {
URL, UMA_CLIENT_NAME, WIDTH_PX, HEIGHT_PX, mCallback);
verify(mCallback).onResult(eq(null));
verify(mCachedImageFetcherImpl, /* Shouldn't make the call at all. */ times(0))
verify(mCachedImageFetcher, /* Shouldn't make the call at all. */ times(0))
.fetchImage(eq(URL), eq(UMA_CLIENT_NAME), eq(WIDTH_PX), eq(HEIGHT_PX), any());
}
@Test
@SmallTest
public void testResize() throws Exception {
Bitmap result =
mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX / 2, HEIGHT_PX / 2);
assertNotEquals(result, mBitmap);
}
@Test
@SmallTest
public void testResizeBailsOutIfSizeIsZeroOrLess() throws Exception {
doReturn(null)
.when(mInMemoryCachedImageFetcher)
.tryToResizeImage(any(), eq(WIDTH_PX), eq(HEIGHT_PX));
Bitmap result = mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX, HEIGHT_PX);
assertNotEquals(result, mBitmap);
result = mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, 0, HEIGHT_PX);
assertEquals(result, mBitmap);
result = mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX, 0);
assertEquals(result, mBitmap);
result = mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, 0, 0);
assertEquals(result, mBitmap);
result = mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, -1, HEIGHT_PX);
assertEquals(result, mBitmap);
result = mInMemoryCachedImageFetcher.tryToResizeImage(mBitmap, WIDTH_PX, -1);
assertEquals(result, mBitmap);
}
@Test
@SmallTest
public void testFetchImageDoesNotCacheAfterDestroy() {
try {
answerFetch(mBitmap, mCachedImageFetcherImpl, true);
answerFetch(mBitmap, mCachedImageFetcher, true);
// No exception should be thrown here when bitmap cache is null.
mInMemoryCachedImageFetcher.fetchImage(
......
......@@ -43,7 +43,7 @@ import java.util.HashMap;
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class ImageFetcherTest {
public class SuggestionsImageFetcherTest {
public static final int IMAGE_SIZE_PX = 100;
public static final String URL_STRING = "http://www.test.com";
@Rule
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment