Commit 158ba455 authored by Sky Malice's avatar Sky Malice Committed by Commit Bot

[Feed] Move ImageLoader url iteration to Java.

This change sets up the ImageLoader to be backed by the
haredImageCache when it becomes available. In the short term, this
change all fixes the order the urls are tried such that we strictly
respect the order provided by the Feed. One downside is that more JNI
calls are made.

Bug: 840578
Change-Id: Ie3309005c995ff9e800add44de6e189752722b78
Reviewed-on: https://chromium-review.googlesource.com/1237333Reviewed-by: default avatarFilip Gorski <fgorski@chromium.org>
Reviewed-by: default avatarGang Wu <gangwu@chromium.org>
Commit-Queue: Sky Malice <skym@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593625}
parent 4921ad7a
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.feed; package org.chromium.chrome.browser.feed;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
...@@ -14,13 +15,11 @@ import android.text.TextUtils; ...@@ -14,13 +15,11 @@ import android.text.TextUtils;
import com.google.android.libraries.feed.common.functional.Consumer; import com.google.android.libraries.feed.common.functional.Consumer;
import com.google.android.libraries.feed.host.imageloader.ImageLoaderApi; import com.google.android.libraries.feed.host.imageloader.ImageLoaderApi;
import org.chromium.base.Callback; import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.feed.FeedImageLoaderBridge.ImageResponse;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.ThumbnailGradient; import org.chromium.chrome.browser.suggestions.ThumbnailGradient;
import java.util.ArrayList; import java.util.Iterator;
import java.util.HashMap;
import java.util.List; import java.util.List;
/** /**
...@@ -66,106 +65,87 @@ public class FeedImageLoader implements ImageLoaderApi { ...@@ -66,106 +65,87 @@ public class FeedImageLoader implements ImageLoaderApi {
public void loadDrawable( public void loadDrawable(
List<String> urls, int widthPx, int heightPx, Consumer<Drawable> consumer) { List<String> urls, int widthPx, int heightPx, Consumer<Drawable> consumer) {
assert mFeedImageLoaderBridge != null; assert mFeedImageLoaderBridge != null;
List<String> assetUrls = new ArrayList<>(); loadDrawableWithIter(urls.iterator(), consumer);
List<String> networkUrls = new ArrayList<>(); }
// Maps position in the networkUrls list to overlay image direction. /** Cleans up FeedImageLoaderBridge. */
HashMap<Integer, Integer> overlayImages = new HashMap<>(); public void destroy() {
assert mFeedImageLoaderBridge != null;
mFeedImageLoaderBridge.destroy();
mFeedImageLoaderBridge = null;
}
/**
* Tries to load the next value in urlsIter, and recursively calls itself on failure to
* continue processing. Being recursive allows resuming after an async call across the bridge.
*
* @param urlsIter The stateful iterator of all urls to load. Each call removes one value.
* @param consumer The callback to be given the first successful image.
*/
private void loadDrawableWithIter(Iterator<String> urlsIter, Consumer<Drawable> consumer) {
assert mFeedImageLoaderBridge != null;
if (!urlsIter.hasNext()) {
// Post to ensure callback is not run synchronously.
ThreadUtils.postOnUiThread(() -> consumer.accept(null));
return;
}
// Since loading APK resource("asset://"") only can be done in Java side, we filter out String url = urlsIter.next();
// asset urls, and pass the other URLs to C++ side. This will change the order of |urls|,
// because we will process asset:// URLs after network URLs, but once
// https://crbug.com/840578 resolved, we can process URLs ordering same as |urls|.
for (String url : urls) {
if (url.startsWith(ASSET_PREFIX)) { if (url.startsWith(ASSET_PREFIX)) {
assetUrls.add(url); Drawable drawable = getAssetDrawable(url);
if (drawable == null) {
loadDrawableWithIter(urlsIter, consumer);
} else {
// Post to ensure callback is not run synchronously.
ThreadUtils.postOnUiThread(() -> consumer.accept(drawable));
}
} else if (url.startsWith(OVERLAY_IMAGE_PREFIX)) { } else if (url.startsWith(OVERLAY_IMAGE_PREFIX)) {
Uri uri = Uri.parse(url); Uri uri = Uri.parse(url);
int direction = overlayDirection(uri);
String sourceUrl = uri.getQueryParameter(OVERLAY_IMAGE_URL_PARAM); String sourceUrl = uri.getQueryParameter(OVERLAY_IMAGE_URL_PARAM);
if (!TextUtils.isEmpty(sourceUrl)) { assert !TextUtils.isEmpty(sourceUrl) : "Overlay image source URL empty";
networkUrls.add(sourceUrl); mFeedImageLoaderBridge.fetchImage(sourceUrl, (Bitmap bitmap) -> {
addOverlayDirectionToMap(overlayImages, networkUrls.size() - 1, uri); if (bitmap == null) {
loadDrawableWithIter(urlsIter, consumer);
} else { } else {
assert false : "Overlay image source URL empty"; consumer.accept(ThumbnailGradient.createDrawableWithGradientIfNeeded(
bitmap, direction, mActivityContext.getResources()));
} }
});
} else { } else {
// Assume this is a regular web image. mFeedImageLoaderBridge.fetchImage(url, (Bitmap bitmap) -> {
networkUrls.add(url); if (bitmap == null) {
} loadDrawableWithIter(urlsIter, consumer);
}
if (networkUrls.size() == 0) {
Drawable drawable = getAssetDrawable(assetUrls);
consumer.accept(drawable);
return;
}
mFeedImageLoaderBridge.fetchImage(networkUrls, new Callback<ImageResponse>() {
@Override
public void onResult(ImageResponse response) {
if (response.bitmap != null) {
Drawable drawable;
if (overlayImages.containsKey(response.imagePositionInList)) {
drawable = ThumbnailGradient.createDrawableWithGradientIfNeeded(
response.bitmap, overlayImages.get(response.imagePositionInList),
mActivityContext.getResources());
} else { } else {
drawable = new BitmapDrawable( consumer.accept(new BitmapDrawable(mActivityContext.getResources(), bitmap));
mActivityContext.getResources(), response.bitmap);
}
consumer.accept(drawable);
return;
}
// Since no image was available for downloading over the network, attempt to load a
// drawable locally.
Drawable drawable = getAssetDrawable(assetUrls);
consumer.accept(drawable);
} }
}); });
} }
/** Cleans up FeedImageLoaderBridge. */
public void destroy() {
assert mFeedImageLoaderBridge != null;
mFeedImageLoaderBridge.destroy();
mFeedImageLoaderBridge = null;
} }
private Drawable getAssetDrawable(List<String> assetUrls) { /**
for (String url : assetUrls) { * @param url The fully qualified name of the resource.
* @return The resource as a Drawable on success, null otherwise.
*/
private Drawable getAssetDrawable(String url) {
String resourceName = url.substring(ASSET_PREFIX.length()); String resourceName = url.substring(ASSET_PREFIX.length());
int resourceId = mActivityContext.getResources().getIdentifier( int id = mActivityContext.getResources().getIdentifier(
resourceName, DRAWABLE_RESOURCE_TYPE, mActivityContext.getPackageName()); resourceName, DRAWABLE_RESOURCE_TYPE, mActivityContext.getPackageName());
if (resourceId != 0) { return id == 0 ? null : AppCompatResources.getDrawable(mActivityContext, id);
Drawable drawable = AppCompatResources.getDrawable(mActivityContext, resourceId);
if (drawable != null) {
return drawable;
}
}
}
return null;
} }
/** /**
* Determine where the thumbnail is located in the card using the "direction" param and add it * Returns where the thumbnail is located in the card using the "direction" query param.
* to the provided HashMap.
* @param overlayImageMap The HashMap used to store the overlay direction.
* @param key The key for the overlay image.
* @param overlayImageUri The URI for the overlay image. * @param overlayImageUri The URI for the overlay image.
* @return The direction in which the thumbnail is located relative to the card.
*/ */
private void addOverlayDirectionToMap( private int overlayDirection(Uri overlayImageUri) {
HashMap<Integer, Integer> overlayImageMap, int key, Uri overlayImageUri) {
String direction = overlayImageUri.getQueryParameter(OVERLAY_IMAGE_DIRECTION_PARAM); String direction = overlayImageUri.getQueryParameter(OVERLAY_IMAGE_DIRECTION_PARAM);
if (TextUtils.equals(direction, OVERLAY_IMAGE_DIRECTION_START)) { assert TextUtils.equals(direction, OVERLAY_IMAGE_DIRECTION_START)
overlayImageMap.put(key, ThumbnailGradient.ThumbnailLocation.START); || TextUtils.equals(direction, OVERLAY_IMAGE_DIRECTION_END)
} else if (TextUtils.equals(direction, OVERLAY_IMAGE_DIRECTION_END)) { : "Overlay image direction must be either start or end";
overlayImageMap.put(key, ThumbnailGradient.ThumbnailLocation.END); return TextUtils.equals(direction, OVERLAY_IMAGE_DIRECTION_START)
} else { ? ThumbnailGradient.ThumbnailLocation.START
assert false : "Overlay image direction must be either start or end"; : ThumbnailGradient.ThumbnailLocation.END;
}
} }
} }
...@@ -7,12 +7,9 @@ package org.chromium.chrome.browser.feed; ...@@ -7,12 +7,9 @@ package org.chromium.chrome.browser.feed;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import java.util.List;
/** /**
* Provides access to native implementations of image loader. * Provides access to native implementations of image loader.
*/ */
...@@ -46,32 +43,14 @@ public class FeedImageLoaderBridge { ...@@ -46,32 +43,14 @@ public class FeedImageLoaderBridge {
* Fetches images for feed. A {@code null} Bitmap is returned if no image is available. The * Fetches images for feed. A {@code null} Bitmap is returned if no image is available. The
* callback is never called synchronously. * callback is never called synchronously.
*/ */
public void fetchImage(List<String> urls, Callback<ImageResponse> callback) { public void fetchImage(String url, Callback<Bitmap> callback) {
assert mNativeFeedImageLoaderBridge != 0; assert mNativeFeedImageLoaderBridge != 0;
nativeFetchImage(mNativeFeedImageLoaderBridge, url, callback);
String[] urlsArray = urls.toArray(new String[urls.size()]);
nativeFetchImage(mNativeFeedImageLoaderBridge, urlsArray, callback);
}
@CalledByNative
public static ImageResponse createImageResponse(int imagePositionInList, Bitmap bitmap) {
return new ImageResponse(imagePositionInList, bitmap);
}
static class ImageResponse {
public int imagePositionInList;
public Bitmap bitmap;
public ImageResponse(int imagePositionInList, Bitmap bitmap) {
this.imagePositionInList = imagePositionInList;
this.bitmap = bitmap;
}
} }
// Native methods // Native methods
private native long nativeInit(Profile profile); private native long nativeInit(Profile profile);
private native void nativeDestroy(long nativeFeedImageLoaderBridge); private native void nativeDestroy(long nativeFeedImageLoaderBridge);
private native void nativeFetchImage( private native void nativeFetchImage(
long nativeFeedImageLoaderBridge, String[] urls, Callback<ImageResponse> callback); long nativeFeedImageLoaderBridge, String url, Callback<Bitmap> callback);
} }
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package org.chromium.chrome.browser.feed; package org.chromium.chrome.browser.feed;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq; import static org.mockito.Mockito.eq;
...@@ -18,6 +19,7 @@ import com.google.android.libraries.feed.common.functional.Consumer; ...@@ -18,6 +19,7 @@ import com.google.android.libraries.feed.common.functional.Consumer;
import com.google.android.libraries.feed.host.imageloader.ImageLoaderApi; import com.google.android.libraries.feed.host.imageloader.ImageLoaderApi;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.AdditionalMatchers; import org.mockito.AdditionalMatchers;
...@@ -26,17 +28,15 @@ import org.mockito.Captor; ...@@ -26,17 +28,15 @@ import org.mockito.Captor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.chromium.base.Callback; import org.chromium.base.Callback;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.feed.FeedImageLoaderBridge.ImageResponse;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.test.support.DisableHistogramsRule;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
/** /**
* Unit tests for {@link FeedImageLoader}. * Unit tests for {@link FeedImageLoader}.
...@@ -48,15 +48,19 @@ public class FeedImageLoaderTest { ...@@ -48,15 +48,19 @@ public class FeedImageLoaderTest {
public static final String HTTP_STRING2 = "http://www.test2.com"; public static final String HTTP_STRING2 = "http://www.test2.com";
public static final String HTTP_STRING3 = "http://www.test3.com"; public static final String HTTP_STRING3 = "http://www.test3.com";
public static final String ASSET_STRING = "asset://logo_avatar_anonymous"; public static final String ASSET_STRING = "asset://logo_avatar_anonymous";
public static final String BAD_ASSET_STRING = "asset://does_not_exist";
public static final String OVERLAY_IMAGE_START = public static final String OVERLAY_IMAGE_START =
"overlay-image://?direction=start&url=https://www.test1.com"; "overlay-image://?direction=start&url=http://www.test1.com";
public static final String OVERLAY_IMAGE_END = public static final String OVERLAY_IMAGE_END =
"overlay-image://?direction=end&url=https://www.test1.com"; "overlay-image://?direction=end&url=http://www.test1.com";
public static final String OVERLAY_IMAGE_MISSING_URL = "overlay-image://?direction=end"; public static final String OVERLAY_IMAGE_MISSING_URL = "overlay-image://?direction=end";
public static final String OVERLAY_IMAGE_MISSING_DIRECTION = public static final String OVERLAY_IMAGE_MISSING_DIRECTION =
"overlay-image://?url=https://www.test1.com"; "overlay-image://?url=http://www.test1.com";
public static final String OVERLAY_IMAGE_BAD_DIRECTION = public static final String OVERLAY_IMAGE_BAD_DIRECTION =
"overlay-image://?direction=east&url=https://www.test1.com"; "overlay-image://?direction=east&url=http://www.test1.com";
@Rule
public DisableHistogramsRule mDisableHistogramsRule = new DisableHistogramsRule();
@Mock @Mock
private FeedImageLoaderBridge mBridge; private FeedImageLoaderBridge mBridge;
...@@ -64,132 +68,162 @@ public class FeedImageLoaderTest { ...@@ -64,132 +68,162 @@ public class FeedImageLoaderTest {
private Consumer<Drawable> mConsumer; private Consumer<Drawable> mConsumer;
@Mock @Mock
private Profile mProfile; private Profile mProfile;
@Mock
private Bitmap mBitmap;
@Captor @Captor
ArgumentCaptor<List<String>> mUrlListArgument; ArgumentCaptor<Callback<Bitmap>> mCallbackArgument;
@Captor
ArgumentCaptor<Callback<ImageResponse>> mCallbackArgument;
private FeedImageLoader mImageLoader; private FeedImageLoader mImageLoader;
private static int sDimX = ImageLoaderApi.DIMENSION_UNKNOWN;
private static int sDimY = ImageLoaderApi.DIMENSION_UNKNOWN;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
doNothing().when(mBridge).init(eq(mProfile)); doNothing().when(mBridge).init(eq(mProfile));
mImageLoader = new FeedImageLoader(mProfile, ContextUtils.getApplicationContext(), mBridge); mImageLoader = new FeedImageLoader(mProfile, ContextUtils.getApplicationContext(), mBridge);
verify(mBridge, times(1)).init(eq(mProfile)); verify(mBridge, times(1)).init(eq(mProfile));
answerFetchImage(-1, null);
} }
private void answerFetchImage(int imagePositionInList, Bitmap bitmap) { private void answerFetchImage(String url, Bitmap bitmap) {
Answer<Void> answer = new Answer<Void>() { doAnswer((InvocationOnMock invocation) -> {
@Override mCallbackArgument.getValue().onResult(bitmap);
public Void answer(InvocationOnMock invocation) {
mCallbackArgument.getValue().onResult(
new ImageResponse(imagePositionInList, bitmap));
return null; return null;
})
.when(mBridge)
.fetchImage(eq(url), mCallbackArgument.capture());
} }
};
doAnswer(answer).when(mBridge).fetchImage( private void loadDrawable(String... urls) {
mUrlListArgument.capture(), mCallbackArgument.capture()); // While normally {@link FeedImageLoader#loadDrawable} guarantees that the return callback
// is invoked asynchronously, this is not the case in tests. It seems that both
// {@link FeedImageLoaderTest#answerFetchImage}, {@link AndroidThreadUtils.postOnUiThread}
// run synchronously.
mImageLoader.loadDrawable(Arrays.asList(urls), ImageLoaderApi.DIMENSION_UNKNOWN,
ImageLoaderApi.DIMENSION_UNKNOWN, mConsumer);
} }
@Test @Test
@SmallTest @SmallTest
public void downloadImageTest() { public void testLoadDrawable() {
List<String> urls = Arrays.asList(HTTP_STRING1); answerFetchImage(HTTP_STRING1, mBitmap);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer);
loadDrawable(HTTP_STRING1);
verify(mBridge, times(1)) verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING1), any());
.fetchImage(mUrlListArgument.capture(), mCallbackArgument.capture()); verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
} }
@Test @Test
@SmallTest @SmallTest
public void onlyNetworkURLSendToBridgeTest() { public void testLoadDrawableFailure() {
List<String> urls = Arrays.asList(HTTP_STRING1, HTTP_STRING2, ASSET_STRING, HTTP_STRING3); answerFetchImage(HTTP_STRING1, null);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer);
List<String> expected_urls = Arrays.asList(HTTP_STRING1, HTTP_STRING2, HTTP_STRING3);
verify(mBridge, times(1)).fetchImage(eq(expected_urls), mCallbackArgument.capture()); loadDrawable(HTTP_STRING1);
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING1), any());
verify(mConsumer, times(1)).accept(eq(null));
} }
@Test @Test
@SmallTest @SmallTest
public void assetImageTest() { public void testLoadDrawableMultiple() {
List<String> urls = Arrays.asList(ASSET_STRING); answerFetchImage(HTTP_STRING1, null);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer); answerFetchImage(HTTP_STRING2, mBitmap);
loadDrawable(HTTP_STRING1, HTTP_STRING2, HTTP_STRING3);
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING1), any());
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING2), any());
verify(mBridge, times(0)).fetchImage(eq(HTTP_STRING3), any());
verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null))); verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
} }
@Test @Test
@SmallTest @SmallTest
public void sendNullIfDownloadFailTest() { public void testLoadDrawableAsset() {
List<String> urls = Arrays.asList(HTTP_STRING1, HTTP_STRING2, HTTP_STRING3); loadDrawable(ASSET_STRING);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer); verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
}
@Test
@SmallTest
public void testLoadDrawableMissingAsset() {
loadDrawable(BAD_ASSET_STRING);
verify(mConsumer, times(1)).accept(eq(null)); verify(mConsumer, times(1)).accept(eq(null));
} }
@Test @Test
@SmallTest @SmallTest
public void nullUrlListTest() { public void testLoadDrawableAssetFallback() {
List<String> urls = Arrays.asList(); loadDrawable(BAD_ASSET_STRING, ASSET_STRING);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer); verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
}
@Test
@SmallTest
public void testLoadDrawableAssetFirst() {
loadDrawable(ASSET_STRING, HTTP_STRING1);
verify(mBridge, times(0)).fetchImage(eq(HTTP_STRING1), any());
verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
}
@Test
@SmallTest
public void testLoadDrawableEmptyList() {
loadDrawable();
verify(mBridge, times(0)).fetchImage(any(), any());
verify(mConsumer, times(1)).accept(eq(null)); verify(mConsumer, times(1)).accept(eq(null));
} }
@Test @Test
@SmallTest @SmallTest
public void overlayImageTest_Start() { public void testLoadDrawableOverlay_Start() {
List<String> urls = Arrays.asList(OVERLAY_IMAGE_START); answerFetchImage(HTTP_STRING1, mBitmap);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer);
verify(mBridge, times(1)) loadDrawable(OVERLAY_IMAGE_START);
.fetchImage(mUrlListArgument.capture(), mCallbackArgument.capture());
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING1), mCallbackArgument.capture());
verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
} }
@Test @Test
@SmallTest @SmallTest
public void overlayImageTest_End() { public void testLoadDrawableOverlay_End() {
List<String> urls = Arrays.asList(OVERLAY_IMAGE_END); answerFetchImage(HTTP_STRING1, mBitmap);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer);
verify(mBridge, times(1)) loadDrawable(OVERLAY_IMAGE_END);
.fetchImage(mUrlListArgument.capture(), mCallbackArgument.capture());
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING1), mCallbackArgument.capture());
verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
} }
@Test(expected = AssertionError.class) @Test(expected = AssertionError.class)
@SmallTest @SmallTest
public void overlayImageTest_MissingUrl() { public void testLoadDrawableOverlay_MissingUrl() {
List<String> urls = Arrays.asList(OVERLAY_IMAGE_MISSING_URL); loadDrawable(OVERLAY_IMAGE_MISSING_URL);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer); }
verify(mConsumer, times(1)).accept(eq(null)); @Test
@SmallTest
public void testLoadDrawableOverlay_Fallback() {
answerFetchImage(HTTP_STRING1, null);
answerFetchImage(HTTP_STRING2, mBitmap);
loadDrawable(OVERLAY_IMAGE_END, HTTP_STRING2);
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING1), mCallbackArgument.capture());
verify(mBridge, times(1)).fetchImage(eq(HTTP_STRING2), mCallbackArgument.capture());
verify(mConsumer, times(1)).accept(AdditionalMatchers.not(eq(null)));
} }
@Test(expected = AssertionError.class) @Test(expected = AssertionError.class)
@SmallTest @SmallTest
public void overlayImageTest_MissingDirection() { public void testLoadDrawableOverlay_MissingDirection() {
List<String> urls = Arrays.asList(OVERLAY_IMAGE_MISSING_DIRECTION); loadDrawable(OVERLAY_IMAGE_MISSING_DIRECTION);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer);
verify(mBridge, times(1))
.fetchImage(mUrlListArgument.capture(), mCallbackArgument.capture());
} }
@Test(expected = AssertionError.class) @Test(expected = AssertionError.class)
@SmallTest @SmallTest
public void overlayImageTest_BadDirection() { public void overlayImageTest_BadDirection() {
List<String> urls = Arrays.asList(OVERLAY_IMAGE_BAD_DIRECTION); loadDrawable(OVERLAY_IMAGE_BAD_DIRECTION);
mImageLoader.loadDrawable(urls, sDimX, sDimY, mConsumer);
verify(mBridge, times(1))
.fetchImage(mUrlListArgument.capture(), mCallbackArgument.capture());
} }
} }
...@@ -7,12 +7,11 @@ ...@@ -7,12 +7,11 @@
#include <jni.h> #include <jni.h>
#include <string> #include <string>
#include <vector> #include <utility>
#include "base/android/callback_android.h" #include "base/android/callback_android.h"
#include "base/android/jni_array.h" #include "base/android/jni_string.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/android/feed/feed_host_service_factory.h" #include "chrome/browser/android/feed/feed_host_service_factory.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_android.h" #include "chrome/browser/profiles/profile_android.h"
...@@ -22,7 +21,6 @@ ...@@ -22,7 +21,6 @@
#include "ui/gfx/android/java_bitmap.h" #include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
using base::android::JavaIntArrayToIntVector;
using base::android::JavaParamRef; using base::android::JavaParamRef;
using base::android::JavaRef; using base::android::JavaRef;
using base::android::ScopedJavaGlobalRef; using base::android::ScopedJavaGlobalRef;
...@@ -57,31 +55,25 @@ void FeedImageLoaderBridge::Destroy(JNIEnv* env, ...@@ -57,31 +55,25 @@ void FeedImageLoaderBridge::Destroy(JNIEnv* env,
void FeedImageLoaderBridge::FetchImage(JNIEnv* j_env, void FeedImageLoaderBridge::FetchImage(JNIEnv* j_env,
const JavaRef<jobject>& j_this, const JavaRef<jobject>& j_this,
const JavaRef<jobjectArray>& j_urls, const JavaRef<jstring>& j_url,
const JavaRef<jobject>& j_callback) { const JavaRef<jobject>& j_callback) {
std::vector<std::string> urls;
base::android::AppendJavaStringArrayToStringVector(j_env, j_urls.obj(),
&urls);
ScopedJavaGlobalRef<jobject> callback(j_callback); ScopedJavaGlobalRef<jobject> callback(j_callback);
std::string url = base::android::ConvertJavaStringToUTF8(j_url);
feed_image_manager_->FetchImage( feed_image_manager_->FetchImage(
urls, base::BindOnce(&FeedImageLoaderBridge::OnImageFetched, {std::move(url)},
base::BindOnce(&FeedImageLoaderBridge::OnImageFetched,
weak_ptr_factory_.GetWeakPtr(), callback)); weak_ptr_factory_.GetWeakPtr(), callback));
} }
void FeedImageLoaderBridge::OnImageFetched( void FeedImageLoaderBridge::OnImageFetched(
ScopedJavaGlobalRef<jobject> callback, ScopedJavaGlobalRef<jobject> callback,
const gfx::Image& image, const gfx::Image& image,
size_t image_position) { size_t ignored) {
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jobject> j_bitmap; ScopedJavaLocalRef<jobject> j_bitmap;
if (!image.IsEmpty()) { if (!image.IsEmpty()) {
j_bitmap = gfx::ConvertToJavaBitmap(image.ToSkBitmap()); j_bitmap = gfx::ConvertToJavaBitmap(image.ToSkBitmap());
} }
RunObjectCallbackAndroid(callback, j_bitmap);
RunObjectCallbackAndroid(callback,
Java_FeedImageLoaderBridge_createImageResponse(
env, image_position, j_bitmap));
} }
} // namespace feed } // namespace feed
...@@ -14,9 +14,7 @@ namespace feed { ...@@ -14,9 +14,7 @@ namespace feed {
class FeedImageManager; class FeedImageManager;
// Native counterpart of FeedImageLoaderBridge.java. Holds non-owning pointers // Native counterpart of FeedImageLoaderBridge.java. Holds non-owning pointers
// to native implementation, to which operations are delegated. Results are // to native implementation, to which operations are delegated. This bridge is
// passed back by a single argument callback so
// base::android::RunObjectCallbackAndroid() can be used. This bridge is
// instantiated, owned, and destroyed from Java. // instantiated, owned, and destroyed from Java.
class FeedImageLoaderBridge { class FeedImageLoaderBridge {
public: public:
...@@ -27,13 +25,13 @@ class FeedImageLoaderBridge { ...@@ -27,13 +25,13 @@ class FeedImageLoaderBridge {
void FetchImage(JNIEnv* j_env, void FetchImage(JNIEnv* j_env,
const base::android::JavaRef<jobject>& j_this, const base::android::JavaRef<jobject>& j_this,
const base::android::JavaRef<jobjectArray>& j_urls, const base::android::JavaRef<jstring>& j_url,
const base::android::JavaRef<jobject>& j_callback); const base::android::JavaRef<jobject>& j_callback);
private: private:
void OnImageFetched(base::android::ScopedJavaGlobalRef<jobject> callback, void OnImageFetched(base::android::ScopedJavaGlobalRef<jobject> callback,
const gfx::Image& image, const gfx::Image& image,
size_t image_position); size_t ignored);
FeedImageManager* feed_image_manager_; FeedImageManager* feed_image_manager_;
......
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