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.
HashMap<Integer, Integer> overlayImages = new HashMap<>();
// Since loading APK resource("asset://"") only can be done in Java side, we filter out
// 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)) {
assetUrls.add(url);
} else if (url.startsWith(OVERLAY_IMAGE_PREFIX)) {
Uri uri = Uri.parse(url);
String sourceUrl = uri.getQueryParameter(OVERLAY_IMAGE_URL_PARAM);
if (!TextUtils.isEmpty(sourceUrl)) {
networkUrls.add(sourceUrl);
addOverlayDirectionToMap(overlayImages, networkUrls.size() - 1, uri);
} else {
assert false : "Overlay image source URL empty";
}
} else {
// Assume this is a regular web image.
networkUrls.add(url);
}
}
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 {
drawable = new BitmapDrawable(
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. */ /** Cleans up FeedImageLoaderBridge. */
public void destroy() { public void destroy() {
assert mFeedImageLoaderBridge != null; assert mFeedImageLoaderBridge != null;
mFeedImageLoaderBridge.destroy(); mFeedImageLoaderBridge.destroy();
mFeedImageLoaderBridge = null; mFeedImageLoaderBridge = null;
} }
private Drawable getAssetDrawable(List<String> assetUrls) { /**
for (String url : assetUrls) { * Tries to load the next value in urlsIter, and recursively calls itself on failure to
String resourceName = url.substring(ASSET_PREFIX.length()); * continue processing. Being recursive allows resuming after an async call across the bridge.
int resourceId = mActivityContext.getResources().getIdentifier( *
resourceName, DRAWABLE_RESOURCE_TYPE, mActivityContext.getPackageName()); * @param urlsIter The stateful iterator of all urls to load. Each call removes one value.
if (resourceId != 0) { * @param consumer The callback to be given the first successful image.
Drawable drawable = AppCompatResources.getDrawable(mActivityContext, resourceId); */
if (drawable != null) { private void loadDrawableWithIter(Iterator<String> urlsIter, Consumer<Drawable> consumer) {
return drawable; assert mFeedImageLoaderBridge != null;
} if (!urlsIter.hasNext()) {
// Post to ensure callback is not run synchronously.
ThreadUtils.postOnUiThread(() -> consumer.accept(null));
return;
}
String url = urlsIter.next();
if (url.startsWith(ASSET_PREFIX)) {
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)) {
Uri uri = Uri.parse(url);
int direction = overlayDirection(uri);
String sourceUrl = uri.getQueryParameter(OVERLAY_IMAGE_URL_PARAM);
assert !TextUtils.isEmpty(sourceUrl) : "Overlay image source URL empty";
mFeedImageLoaderBridge.fetchImage(sourceUrl, (Bitmap bitmap) -> {
if (bitmap == null) {
loadDrawableWithIter(urlsIter, consumer);
} else {
consumer.accept(ThumbnailGradient.createDrawableWithGradientIfNeeded(
bitmap, direction, mActivityContext.getResources()));
}
});
} else {
mFeedImageLoaderBridge.fetchImage(url, (Bitmap bitmap) -> {
if (bitmap == null) {
loadDrawableWithIter(urlsIter, consumer);
} else {
consumer.accept(new BitmapDrawable(mActivityContext.getResources(), bitmap));
}
});
} }
return null;
} }
/** /**
* Determine where the thumbnail is located in the card using the "direction" param and add it * @param url The fully qualified name of the resource.
* to the provided HashMap. * @return The resource as a Drawable on success, null otherwise.
* @param overlayImageMap The HashMap used to store the overlay direction. */
* @param key The key for the overlay image. private Drawable getAssetDrawable(String url) {
String resourceName = url.substring(ASSET_PREFIX.length());
int id = mActivityContext.getResources().getIdentifier(
resourceName, DRAWABLE_RESOURCE_TYPE, mActivityContext.getPackageName());
return id == 0 ? null : AppCompatResources.getDrawable(mActivityContext, id);
}
/**
* Returns where the thumbnail is located in the card using the "direction" query param.
* @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);
} }
...@@ -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)},
weak_ptr_factory_.GetWeakPtr(), callback)); base::BindOnce(&FeedImageLoaderBridge::OnImageFetched,
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