[Feed] Implement Content Storage API in Java
Implement Content Storage API in Java and JNI bridge.
Bug:828935
Change-Id: I6c70d18cfde0891f5c9533458bf453a891b4b2be
Reviewed-on: https://chromium-review.googlesource.com/1137739
Commit-Queue: Gang Wu <gangwu@chromium.org>
Reviewed-by: Sky Malice <skym@chromium.org>
Reviewed-by: Filip Gorski <fgorski@chromium.org>
Cr-Commit-Position: refs/heads/master@{#576950}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedContentStorage.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedContentStorage.java
new file mode 100644
index 0000000..9926d3a
--- /dev/null
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedContentStorage.java
@@ -0,0 +1,144 @@
+// 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.feed;
+
+import com.google.android.libraries.feed.common.Result;
+import com.google.android.libraries.feed.common.functional.Consumer;
+import com.google.android.libraries.feed.host.storage.CommitResult;
+import com.google.android.libraries.feed.host.storage.ContentMutation;
+import com.google.android.libraries.feed.host.storage.ContentOperation;
+import com.google.android.libraries.feed.host.storage.ContentOperation.Delete;
+import com.google.android.libraries.feed.host.storage.ContentOperation.DeleteByPrefix;
+import com.google.android.libraries.feed.host.storage.ContentOperation.Type;
+import com.google.android.libraries.feed.host.storage.ContentOperation.Upsert;
+import com.google.android.libraries.feed.host.storage.ContentStorage;
+
+import org.chromium.base.Callback;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Implementation of {@link ContentStorage} that persisits data on native side.
+ */
+public class FeedContentStorage implements ContentStorage {
+ private FeedStorageBridge mFeedStorageBridge;
+
+ private static class StorageCallback<T> implements Callback<T> {
+ private final Consumer<Result<T>> mConsumer;
+
+ public StorageCallback(Consumer<Result<T>> consumer) {
+ mConsumer = consumer;
+ }
+
+ @Override
+ public void onResult(T data) {
+ // TODO(gangwu): Need to handle failure case.
+ mConsumer.accept(Result.success(data));
+ }
+ }
+
+ private static class CommitCallback implements Callback<Boolean> {
+ private final Consumer<CommitResult> mConsumer;
+ private int mOperationsLeft;
+ private boolean mSuccess;
+
+ CommitCallback(Consumer<CommitResult> consumer, int operationsCount) {
+ mConsumer = consumer;
+ mOperationsLeft = operationsCount;
+ mSuccess = true;
+ }
+
+ @Override
+ public void onResult(Boolean result) {
+ --mOperationsLeft;
+ assert mOperationsLeft >= 0;
+
+ mSuccess &= result.booleanValue();
+ // TODO(gangwu): if |result| is failure, all other operation should halt immediately.
+ if (mOperationsLeft == 0) {
+ if (mSuccess) {
+ mConsumer.accept(CommitResult.SUCCESS);
+ } else {
+ mConsumer.accept(CommitResult.FAILURE);
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates a {@link FeedContentStorage} for storing content for the current user.
+ *
+ * @param bridge {@link FeedStorageBridge} implementation can handle content storage request.
+ */
+ public FeedContentStorage(FeedStorageBridge bridge) {
+ mFeedStorageBridge = bridge;
+ }
+
+ /** Cleans up {@link FeedContentStorage}. */
+ public void destroy() {
+ assert mFeedStorageBridge != null;
+ mFeedStorageBridge.destroy();
+ mFeedStorageBridge = null;
+ }
+
+ @Override
+ public void get(List<String> keys, Consumer < Result < Map<String, byte[]>>> consumer) {
+ assert mFeedStorageBridge != null;
+ mFeedStorageBridge.loadContent(keys, new StorageCallback<Map<String, byte[]>>(consumer));
+ }
+
+ @Override
+ public void getAll(String prefix, Consumer < Result < Map<String, byte[]>>> consumer) {
+ assert mFeedStorageBridge != null;
+ mFeedStorageBridge.loadContentByPrefix(
+ prefix, new StorageCallback<Map<String, byte[]>>(consumer));
+ }
+
+ @Override
+ public void commit(ContentMutation mutation, Consumer<CommitResult> consumer) {
+ assert mFeedStorageBridge != null;
+
+ CommitCallback callback = new CommitCallback(consumer, mutation.getOperations().size());
+ for (ContentOperation operation : mutation.getOperations()) {
+ switch (operation.getType()) {
+ case Type.UPSERT:
+ // TODO(gangwu): If upserts are continuous, we should conbine them into one
+ // array, and then send to native side.
+ Upsert upsert = (Upsert) operation;
+ String[] upsertKeys = {upsert.getKey()};
+ byte[][] upsertData = {upsert.getValue()};
+ mFeedStorageBridge.saveContent(upsertKeys, upsertData, callback);
+ break;
+ case Type.DELETE:
+ // TODO(gangwu): If deletes are continuous, we should conbine them into one
+ // array, and then send to native side.
+ Delete delete = (Delete) operation;
+ List<String> deleteKeys = Collections.singletonList(delete.getKey());
+ mFeedStorageBridge.deleteContent(deleteKeys, callback);
+ break;
+ case Type.DELETE_BY_PREFIX:
+ DeleteByPrefix deleteByPrefix = (DeleteByPrefix) operation;
+ String prefix = deleteByPrefix.getPrefix();
+ mFeedStorageBridge.deleteContentByPrefix(prefix, callback);
+ break;
+ case Type.DELETE_ALL:
+ mFeedStorageBridge.deleteAllContent(callback);
+ break;
+ default:
+ // Unsupport type of operations, so cannot performance the operation.
+ callback.onResult(false);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void getAllKeys(Consumer < Result < List<String>>> consumer) {
+ assert mFeedStorageBridge != null;
+ mFeedStorageBridge.loadAllContentKeys(new StorageCallback<List<String>>(consumer));
+ }
+}
diff --git a/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStorageBridge.java b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStorageBridge.java
new file mode 100644
index 0000000..b45d9719
--- /dev/null
+++ b/chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStorageBridge.java
@@ -0,0 +1,116 @@
+// 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.feed;
+
+import org.chromium.base.Callback;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.JNINamespace;
+import org.chromium.chrome.browser.profiles.Profile;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Provides access to native implementations of content storage and journal storage.
+ */
+@JNINamespace("feed")
+public class FeedStorageBridge {
+ private long mNativeFeedStorageBridge;
+
+ /**
+ * Creates a {@link FeedStorageBridge} for accessing native content and journal storage
+ * implementation for the current user, and initial native side bridge.
+ *
+ * @param profile {@link Profile} of the user we are rendering the Feed for.
+ */
+ public FeedStorageBridge() {}
+
+ /**
+ * Inits native side bridge.
+ *
+ * @param profile {@link Profile} of the user we are rendering the Feed for.
+ */
+ public void init(Profile profile) {
+ mNativeFeedStorageBridge = nativeInit(profile);
+ }
+
+ /** Cleans up native half of this bridge. */
+ public void destroy() {
+ assert mNativeFeedStorageBridge != 0;
+ nativeDestroy(mNativeFeedStorageBridge);
+ mNativeFeedStorageBridge = 0;
+ }
+
+ public void loadContent(List<String> keys, Callback<Map<String, byte[]>> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ String[] keysArray = keys.toArray(new String[keys.size()]);
+ nativeLoadContent(mNativeFeedStorageBridge, keysArray, callback);
+ }
+
+ public void loadContentByPrefix(String prefix, Callback<Map<String, byte[]>> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ nativeLoadContentByPrefix(mNativeFeedStorageBridge, prefix, callback);
+ }
+
+ public void loadAllContentKeys(Callback<List<String>> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ nativeLoadAllContentKeys(mNativeFeedStorageBridge, callback);
+ }
+
+ public void saveContent(String[] keys, byte[][] data, Callback<Boolean> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ nativeSaveContent(mNativeFeedStorageBridge, keys, data, callback);
+ }
+
+ public void deleteContent(List<String> keys, Callback<Boolean> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ String[] keysArray = keys.toArray(new String[keys.size()]);
+ nativeDeleteContent(mNativeFeedStorageBridge, keysArray, callback);
+ }
+
+ public void deleteContentByPrefix(String prefix, Callback<Boolean> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ nativeDeleteContentByPrefix(mNativeFeedStorageBridge, prefix, callback);
+ }
+
+ public void deleteAllContent(Callback<Boolean> callback) {
+ assert mNativeFeedStorageBridge != 0;
+ nativeDeleteAllContent(mNativeFeedStorageBridge, callback);
+ }
+
+ @CalledByNative
+ private static Object createKeyAndDataMap(String[] keys, byte[][] data) {
+ assert keys.length == data.length;
+ Map<String, byte[]> valueMap = new HashMap<>(keys.length);
+ for (int i = 0; i < keys.length && i < data.length; ++i) {
+ valueMap.put(keys[i], data[i]);
+ }
+ return valueMap;
+ }
+
+ @CalledByNative
+ private static List<String> createJavaList(String[] keys) {
+ return Arrays.asList(keys);
+ }
+
+ private native long nativeInit(Profile profile);
+ private native void nativeDestroy(long nativeFeedStorageBridge);
+ private native void nativeLoadContent(
+ long nativeFeedStorageBridge, String[] keys, Callback<Map<String, byte[]>> callback);
+ private native void nativeLoadContentByPrefix(
+ long nativeFeedStorageBridge, String prefix, Callback<Map<String, byte[]>> callback);
+ private native void nativeLoadAllContentKeys(
+ long nativeFeedStorageBridge, Callback<List<String>> callback);
+ private native void nativeSaveContent(
+ long nativeFeedStorageBridge, String[] keys, byte[][] data, Callback<Boolean> callback);
+ private native void nativeDeleteContent(
+ long nativeFeedStorageBridge, String[] keys, Callback<Boolean> callback);
+ private native void nativeDeleteContentByPrefix(
+ long nativeFeedStorageBridge, String prefix, Callback<Boolean> callback);
+ private native void nativeDeleteAllContent(
+ long nativeFeedStorageBridge, Callback<Boolean> callback);
+}
diff --git a/chrome/android/feed/feed_java_sources.gni b/chrome/android/feed/feed_java_sources.gni
index bb4ce6e..ebe41f4 100644
--- a/chrome/android/feed/feed_java_sources.gni
+++ b/chrome/android/feed/feed_java_sources.gni
@@ -9,6 +9,7 @@
feed_deps = [ "//third_party/feed:feed_lib_java" ]
feed_java_sources = [
+ "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedContentStorage.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedEventReporter.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoader.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoaderBridge.java",
@@ -18,6 +19,7 @@
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedProcessScopeFactory.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedRefreshTask.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSchedulerBridge.java",
+ "//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStorageBridge.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/StreamLifecycleManager.java",
"//chrome/android/feed/core/java/src/org/chromium/chrome/browser/feed/action/FeedActionHandler.java",
]
@@ -25,6 +27,7 @@
feed_srcjar_deps = [ "//components/feed/core:feed_core_java_enums_srcjar" ]
feed_junit_test_java_sources = [
+ "junit/src/org/chromium/chrome/browser/feed/FeedContentStorageTest.java",
"junit/src/org/chromium/chrome/browser/feed/FeedImageLoaderTest.java",
"junit/src/org/chromium/chrome/browser/feed/StreamLifecycleManagerTest.java",
]
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedContentStorageTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedContentStorageTest.java
new file mode 100644
index 0000000..36ae6f4
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/feed/FeedContentStorageTest.java
@@ -0,0 +1,245 @@
+// 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.feed;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.filters.SmallTest;
+
+import com.google.android.libraries.feed.common.Result;
+import com.google.android.libraries.feed.common.functional.Consumer;
+import com.google.android.libraries.feed.host.storage.CommitResult;
+import com.google.android.libraries.feed.host.storage.ContentMutation;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.Callback;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.profiles.Profile;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Unit tests for {@link FeedContentStorage}.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class FeedContentStorageTest {
+ public static final String CONTENT_KEY1 = "CONTENT_KEY_1";
+ public static final String CONTENT_KEY2 = "CONTENT_KEY_2";
+ public static final String CONTENT_KEY3 = "CONTENT_KEY_3";
+ public static final byte[] CONTENT_DATA1 = "CONTENT_DATA_1".getBytes(Charset.forName("UTF-8"));
+ public static final byte[] CONTENT_DATA2 = "CONTENT_DATA_2".getBytes(Charset.forName("UTF-8"));
+ public static final byte[] CONTENT_DATA3 = "CONTENT_DATA_3".getBytes(Charset.forName("UTF-8"));
+
+ @Mock
+ private FeedStorageBridge mBridge;
+ @Mock
+ private Consumer<CommitResult> mBooleanConsumer;
+ @Mock
+ private Consumer < Result < List<String>>> mListConsumer;
+ @Mock
+ private Consumer < Result < Map<String, byte[]>>> mMapConsumer;
+ @Mock
+ private Profile mProfile;
+ @Captor
+ private ArgumentCaptor<CommitResult> mCommitResultCaptor;
+ @Captor
+ private ArgumentCaptor < Result < List<String>>> mStringListCaptor;
+ @Captor
+ private ArgumentCaptor < Result < Map<String, byte[]>>> mMapCaptor;
+ @Captor
+ private ArgumentCaptor<String> mStringArgument;
+ @Captor
+ private ArgumentCaptor<List<String>> mStringListArgument;
+ @Captor
+ private ArgumentCaptor<String[]> mStringArrayArgument;
+ @Captor
+ private ArgumentCaptor<byte[][]> mByteArrayOfArrayArgument;
+ @Captor
+ private ArgumentCaptor<Callback<Boolean>> mBooleanCallbackArgument;
+ @Captor
+ private ArgumentCaptor < Callback < List<String>>> mStringListCallbackArgument;
+ @Captor
+ private ArgumentCaptor < Callback < Map<String, byte[]>>> mMapCallbackArgument;
+
+ private FeedContentStorage mContentStorage;
+
+ private Answer<Void> createMapAnswer(Map<String, byte[]> map) {
+ return new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ mMapCallbackArgument.getValue().onResult(map);
+ return null;
+ }
+ };
+ }
+
+ private Answer<Void> createStringListAnswer(List<String> stringList) {
+ return new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ mStringListCallbackArgument.getValue().onResult(stringList);
+ return null;
+ }
+ };
+ }
+
+ private Answer<Void> createBooleanAnswer(Boolean bool) {
+ return new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) {
+ mBooleanCallbackArgument.getValue().onResult(bool);
+ return null;
+ }
+ };
+ }
+
+ private void verifyMapResult(Map<String, byte[]> expectedMap, boolean expectedBoolean,
+ Result<Map<String, byte[]>> actualResult) {
+ assertEquals(expectedBoolean, actualResult.isSuccessful());
+ if (!expectedBoolean) return;
+
+ Map<String, byte[]> actualMap = actualResult.getValue();
+ assertEquals(expectedMap.size(), actualMap.size());
+ for (Map.Entry<String, byte[]> entry : expectedMap.entrySet()) {
+ assertTrue(actualMap.containsKey(entry.getKey()));
+ assertEquals(entry.getValue(), actualMap.get(entry.getKey()));
+ }
+ }
+
+ private void verifyStringListResult(
+ List<String> expectedList, boolean expectedBoolean, Result<List<String>> actualResult) {
+ assertEquals(expectedBoolean, actualResult.isSuccessful());
+ if (!expectedBoolean) return;
+
+ List<String> actualList = actualResult.getValue();
+ assertEquals(expectedList.size(), actualList.size());
+ for (String expectedString : expectedList) {
+ assertTrue(actualList.contains(expectedString));
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doNothing().when(mBridge).init(eq(mProfile));
+ mBridge.init(mProfile);
+ mContentStorage = new FeedContentStorage(mBridge);
+ }
+
+ @Test
+ @SmallTest
+ public void getTest() {
+ Map<String, byte[]> answerMap = new HashMap<>();
+ answerMap.put(CONTENT_KEY1, CONTENT_DATA1);
+ Answer<Void> answer = createMapAnswer(answerMap);
+ doAnswer(answer).when(mBridge).loadContent(
+ mStringListArgument.capture(), mMapCallbackArgument.capture());
+ List<String> keys = Arrays.asList(CONTENT_KEY1, CONTENT_KEY2);
+
+ mContentStorage.get(keys, mMapConsumer);
+ verify(mBridge, times(1)).loadContent(eq(keys), mMapCallbackArgument.capture());
+ verify(mMapConsumer, times(1)).accept(mMapCaptor.capture());
+ verifyMapResult(answerMap, true, mMapCaptor.getValue());
+ }
+
+ @Test
+ @SmallTest
+ public void getAllTest() {
+ Map<String, byte[]> answerMap = new HashMap<>();
+ answerMap.put(CONTENT_KEY1, CONTENT_DATA1);
+ answerMap.put(CONTENT_KEY2, CONTENT_DATA2);
+ answerMap.put(CONTENT_KEY3, CONTENT_DATA3);
+ Answer<Void> answer = createMapAnswer(answerMap);
+ doAnswer(answer).when(mBridge).loadContentByPrefix(
+ mStringArgument.capture(), mMapCallbackArgument.capture());
+
+ mContentStorage.getAll(CONTENT_KEY1, mMapConsumer);
+ verify(mBridge, times(1))
+ .loadContentByPrefix(eq(CONTENT_KEY1), mMapCallbackArgument.capture());
+ verify(mMapConsumer, times(1)).accept(mMapCaptor.capture());
+ verifyMapResult(answerMap, true, mMapCaptor.getValue());
+ }
+
+ @Test
+ @SmallTest
+ public void getAllKeysTest() {
+ List<String> answerStrings = Arrays.asList(CONTENT_KEY1, CONTENT_KEY2, CONTENT_KEY3);
+ Answer<Void> answer = createStringListAnswer(answerStrings);
+ doAnswer(answer).when(mBridge).loadAllContentKeys(mStringListCallbackArgument.capture());
+
+ mContentStorage.getAllKeys(mListConsumer);
+ verify(mBridge, times(1)).loadAllContentKeys(mStringListCallbackArgument.capture());
+ verify(mListConsumer, times(1)).accept(mStringListCaptor.capture());
+ verifyStringListResult(answerStrings, true, mStringListCaptor.getValue());
+ }
+
+ @Test
+ @SmallTest
+ public void commitTest() {
+ Answer<Void> answerSaveContent = createBooleanAnswer(true);
+ doAnswer(answerSaveContent)
+ .when(mBridge)
+ .saveContent(mStringArrayArgument.capture(), mByteArrayOfArrayArgument.capture(),
+ mBooleanCallbackArgument.capture());
+
+ Answer<Void> answerDeleteContent = createBooleanAnswer(true);
+ doAnswer(answerDeleteContent)
+ .when(mBridge)
+ .deleteContent(mStringListArgument.capture(), mBooleanCallbackArgument.capture());
+
+ Answer<Void> answerDeleteContentByPrefix = createBooleanAnswer(true);
+ doAnswer(answerDeleteContentByPrefix)
+ .when(mBridge)
+ .deleteContentByPrefix(
+ mStringArgument.capture(), mBooleanCallbackArgument.capture());
+
+ Answer<Void> answerDeleteAllContent = createBooleanAnswer(true);
+ doAnswer(answerDeleteAllContent)
+ .when(mBridge)
+ .deleteAllContent(mBooleanCallbackArgument.capture());
+
+ mContentStorage.commit(new ContentMutation.Builder()
+ .upsert(CONTENT_KEY1, CONTENT_DATA1)
+ .delete(CONTENT_KEY2)
+ .deleteByPrefix(CONTENT_KEY3)
+ .deleteAll()
+ .build(),
+ mBooleanConsumer);
+ verify(mBridge, times(1))
+ .saveContent(mStringArrayArgument.capture(), mByteArrayOfArrayArgument.capture(),
+ mBooleanCallbackArgument.capture());
+ verify(mBridge, times(1))
+ .deleteContent(mStringListArgument.capture(), mBooleanCallbackArgument.capture());
+ verify(mBridge, times(1))
+ .deleteContentByPrefix(
+ mStringArgument.capture(), mBooleanCallbackArgument.capture());
+ verify(mBridge, times(1)).deleteAllContent(mBooleanCallbackArgument.capture());
+
+ verify(mBooleanConsumer, times(1)).accept(mCommitResultCaptor.capture());
+ CommitResult commitResult = mCommitResultCaptor.getValue();
+ assertEquals(CommitResult.SUCCESS, commitResult);
+ }
+}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 3ed3fcd..d597743 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4334,6 +4334,8 @@
"android/feed/feed_network_bridge.h",
"android/feed/feed_scheduler_bridge.cc",
"android/feed/feed_scheduler_bridge.h",
+ "android/feed/feed_storage_bridge.cc",
+ "android/feed/feed_storage_bridge.h",
]
deps += [ "//components/feed/core:feed_core" ]
}
@@ -4595,6 +4597,7 @@
"../android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedImageLoaderBridge.java",
"../android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedNetworkBridge.java",
"../android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedSchedulerBridge.java",
+ "../android/feed/core/java/src/org/chromium/chrome/browser/feed/FeedStorageBridge.java",
]
}
diff --git a/chrome/browser/android/feed/feed_storage_bridge.cc b/chrome/browser/android/feed/feed_storage_bridge.cc
new file mode 100644
index 0000000..3393ad2
--- /dev/null
+++ b/chrome/browser/android/feed/feed_storage_bridge.cc
@@ -0,0 +1,198 @@
+// 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.
+
+#include "chrome/browser/android/feed/feed_storage_bridge.h"
+
+#include <jni.h>
+
+#include <string>
+#include <vector>
+
+#include "base/android/callback_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.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/profiles/profile.h"
+#include "chrome/browser/profiles/profile_android.h"
+#include "components/feed/core/feed_host_service.h"
+#include "components/feed/core/feed_storage_database.h"
+#include "jni/FeedStorageBridge_jni.h"
+#include "ui/gfx/android/java_bitmap.h"
+#include "ui/gfx/image/image.h"
+
+namespace feed {
+
+using base::android::AppendJavaStringArrayToStringVector;
+using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
+using base::android::JavaArrayOfByteArrayToStringVector;
+using base::android::JavaIntArrayToIntVector;
+using base::android::JavaRef;
+using base::android::JavaParamRef;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+using base::android::ToJavaArrayOfByteArray;
+using base::android::ToJavaArrayOfStrings;
+
+static jlong JNI_FeedStorageBridge_Init(
+ JNIEnv* env,
+ const JavaParamRef<jobject>& j_this,
+ const JavaParamRef<jobject>& j_profile) {
+ Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
+ FeedHostService* host_service =
+ FeedHostServiceFactory::GetForBrowserContext(profile);
+ DCHECK(host_service);
+ FeedStorageDatabase* feed_storage_database =
+ host_service->GetStorageDatabase();
+ DCHECK(feed_storage_database);
+ FeedStorageBridge* native_storage_bridge =
+ new FeedStorageBridge(feed_storage_database);
+ return reinterpret_cast<intptr_t>(native_storage_bridge);
+}
+
+FeedStorageBridge::FeedStorageBridge(FeedStorageDatabase* feed_storage_database)
+ : feed_storage_database_(feed_storage_database), weak_ptr_factory_(this) {}
+
+FeedStorageBridge::~FeedStorageBridge() = default;
+
+void FeedStorageBridge::Destroy(JNIEnv* env, const JavaRef<jobject>& j_this) {
+ delete this;
+}
+
+void FeedStorageBridge::LoadContent(JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jobjectArray>& j_keys,
+ const JavaRef<jobject>& j_callback) {
+ std::vector<std::string> keys;
+ AppendJavaStringArrayToStringVector(j_env, j_keys.obj(), &keys);
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ feed_storage_database_->LoadContent(
+ keys, base::BindOnce(&FeedStorageBridge::OnLoadContentDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::LoadContentByPrefix(
+ JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jstring>& j_prefix,
+ const JavaRef<jobject>& j_callback) {
+ std::string prefix = ConvertJavaStringToUTF8(j_env, j_prefix);
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ feed_storage_database_->LoadContentByPrefix(
+ prefix, base::BindOnce(&FeedStorageBridge::OnLoadContentDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::LoadAllContentKeys(JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jobject>& j_callback) {
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ feed_storage_database_->LoadAllContentKeys(
+ base::BindOnce(&FeedStorageBridge::OnLoadAllContentKeysDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::SaveContent(JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jobjectArray>& j_keys,
+ const JavaRef<jobjectArray>& j_data,
+ const JavaRef<jobject>& j_callback) {
+ std::vector<std::string> keys;
+ std::vector<std::string> data;
+ AppendJavaStringArrayToStringVector(j_env, j_keys.obj(), &keys);
+ JavaArrayOfByteArrayToStringVector(j_env, j_data.obj(), &data);
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ DCHECK_EQ(keys.size(), data.size());
+ std::vector<FeedStorageDatabase::KeyAndData> pairs;
+ for (size_t i = 0; i < keys.size() && i < data.size(); ++i) {
+ pairs.emplace_back(keys[i], data[i]);
+ }
+
+ feed_storage_database_->SaveContent(
+ std::move(pairs),
+ base::BindOnce(&FeedStorageBridge::OnStorageCommitDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::DeleteContent(JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jobjectArray>& j_keys,
+ const JavaRef<jobject>& j_callback) {
+ std::vector<std::string> keys;
+ AppendJavaStringArrayToStringVector(j_env, j_keys.obj(), &keys);
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ feed_storage_database_->DeleteContent(
+ keys, base::BindOnce(&FeedStorageBridge::OnStorageCommitDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::DeleteContentByPrefix(
+ JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jstring>& j_prefix,
+ const JavaRef<jobject>& j_callback) {
+ std::string prefix = ConvertJavaStringToUTF8(j_env, j_prefix);
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ feed_storage_database_->DeleteContentByPrefix(
+ prefix, base::BindOnce(&FeedStorageBridge::OnStorageCommitDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::DeleteAllContent(JNIEnv* j_env,
+ const JavaRef<jobject>& j_this,
+ const JavaRef<jobject>& j_callback) {
+ ScopedJavaGlobalRef<jobject> callback(j_callback);
+
+ feed_storage_database_->DeleteAllContent(
+ base::BindOnce(&FeedStorageBridge::OnStorageCommitDone,
+ weak_ptr_factory_.GetWeakPtr(), callback));
+}
+
+void FeedStorageBridge::OnLoadContentDone(
+ ScopedJavaGlobalRef<jobject> callback,
+ std::vector<FeedStorageDatabase::KeyAndData> pairs) {
+ std::vector<std::string> keys;
+ std::vector<std::string> data;
+ for (auto pair : pairs) {
+ keys.push_back(std::move(pair.first));
+ data.push_back(std::move(pair.second));
+ }
+
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobjectArray> j_keys = ToJavaArrayOfStrings(env, keys);
+ ScopedJavaLocalRef<jobjectArray> j_data = ToJavaArrayOfByteArray(env, data);
+
+ // Ceate Java Map by JNI call.
+ ScopedJavaLocalRef<jobject> j_pairs =
+ Java_FeedStorageBridge_createKeyAndDataMap(env, j_keys, j_data);
+ RunObjectCallbackAndroid(callback, j_pairs);
+}
+
+void FeedStorageBridge::OnLoadAllContentKeysDone(
+ ScopedJavaGlobalRef<jobject> callback,
+ std::vector<std::string> keys) {
+ JNIEnv* env = AttachCurrentThread();
+ ScopedJavaLocalRef<jobjectArray> j_keys = ToJavaArrayOfStrings(env, keys);
+
+ // Ceate Java List by JNI call.
+ ScopedJavaLocalRef<jobject> j_keys_list =
+ Java_FeedStorageBridge_createJavaList(env, j_keys);
+ RunObjectCallbackAndroid(callback, j_keys_list);
+}
+
+void FeedStorageBridge::OnStorageCommitDone(
+ ScopedJavaGlobalRef<jobject> callback,
+ bool success) {
+ RunBooleanCallbackAndroid(callback, success);
+}
+
+} // namespace feed
diff --git a/chrome/browser/android/feed/feed_storage_bridge.h b/chrome/browser/android/feed/feed_storage_bridge.h
new file mode 100644
index 0000000..19b7d8f
--- /dev/null
+++ b/chrome/browser/android/feed/feed_storage_bridge.h
@@ -0,0 +1,75 @@
+// 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.
+
+#ifndef CHROME_BROWSER_ANDROID_FEED_FEED_STORAGE_BRIDGE_H_
+#define CHROME_BROWSER_ANDROID_FEED_FEED_STORAGE_BRIDGE_H_
+
+#include "base/android/scoped_java_ref.h"
+#include "base/memory/weak_ptr.h"
+#include "components/feed/core/feed_storage_database.h"
+
+namespace feed {
+
+class FeedStorageDatabase;
+
+// Native counterpart of FeedStorageBridge.java. Holds non-owning pointers
+// to native implementation, to which operations are delegated. Results are
+// passed back by a single argument callback so
+// base::android::RunBooleanCallbackAndroid() and
+// base::android::RunObjectCallbackAndroid() can be used. This bridge is
+// instantiated, owned, and destroyed from Java.
+class FeedStorageBridge {
+ public:
+ explicit FeedStorageBridge(FeedStorageDatabase* feed_Storage_database);
+ ~FeedStorageBridge();
+
+ void Destroy(JNIEnv* j_env, const base::android::JavaRef<jobject>& j_this);
+
+ void LoadContent(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jobjectArray>& j_keys,
+ const base::android::JavaRef<jobject>& j_callback);
+ void LoadContentByPrefix(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jstring>& j_prefix,
+ const base::android::JavaRef<jobject>& j_callback);
+ void LoadAllContentKeys(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jobject>& j_callback);
+ void SaveContent(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jobjectArray>& j_keys,
+ const base::android::JavaRef<jobjectArray>& j_data,
+ const base::android::JavaRef<jobject>& j_callback);
+ void DeleteContent(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jobjectArray>& j_keys,
+ const base::android::JavaRef<jobject>& j_callback);
+ void DeleteContentByPrefix(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jstring>& j_prefix,
+ const base::android::JavaRef<jobject>& j_callback);
+ void DeleteAllContent(JNIEnv* j_env,
+ const base::android::JavaRef<jobject>& j_this,
+ const base::android::JavaRef<jobject>& j_callback);
+
+ private:
+ void OnLoadContentDone(base::android::ScopedJavaGlobalRef<jobject> callback,
+ std::vector<FeedStorageDatabase::KeyAndData> pairs);
+ void OnLoadAllContentKeysDone(
+ base::android::ScopedJavaGlobalRef<jobject> callback,
+ std::vector<std::string> keys);
+ void OnStorageCommitDone(base::android::ScopedJavaGlobalRef<jobject> callback,
+ bool success);
+
+ FeedStorageDatabase* feed_storage_database_;
+
+ base::WeakPtrFactory<FeedStorageBridge> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FeedStorageBridge);
+};
+
+} // namespace feed
+
+#endif // CHROME_BROWSER_ANDROID_FEED_FEED_STORAGE_BRIDGE_H_