summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java7
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java10
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java5
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetCardItemViewHolder.java2
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java54
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsController.java59
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsManager.java157
-rw-r--r--chrome/browser/android/DEPS1
-rw-r--r--chrome/browser/android/chrome_jni_registrar.cc4
-rw-r--r--chrome/browser/android/ntp_snippets_bridge.cc84
-rw-r--r--chrome/browser/android/ntp_snippets_bridge.h44
-rw-r--r--chrome/browser/android/ntp_snippets_controller.cc34
-rw-r--r--chrome/browser/android/ntp_snippets_controller.h21
-rw-r--r--chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc28
-rw-r--r--chrome/chrome_browser.gypi8
-rw-r--r--components/BUILD.gn1
-rw-r--r--components/ntp_snippets.gypi2
-rw-r--r--components/ntp_snippets/BUILD.gn8
-rw-r--r--components/ntp_snippets/DEPS5
-rw-r--r--components/ntp_snippets/ntp_snippet.cc4
-rw-r--r--components/ntp_snippets/ntp_snippet.h3
-rw-r--r--components/ntp_snippets/ntp_snippets_fetcher.cc191
-rw-r--r--components/ntp_snippets/ntp_snippets_fetcher.h94
-rw-r--r--components/ntp_snippets/ntp_snippets_service.cc51
-rw-r--r--components/ntp_snippets/ntp_snippets_service.h49
-rw-r--r--components/ntp_snippets/ntp_snippets_service_unittest.cc71
-rw-r--r--ios/chrome/browser/ntp_snippets/ios_chrome_ntp_snippets_service_factory.cc27
27 files changed, 861 insertions, 163 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
index ed00ff5..9d1eb0a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -89,6 +89,7 @@ import org.chromium.chrome.browser.metrics.StartupMetrics;
import org.chromium.chrome.browser.metrics.UmaSessionStats;
import org.chromium.chrome.browser.nfc.BeamController;
import org.chromium.chrome.browser.nfc.BeamProvider;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsController;
import org.chromium.chrome.browser.offlinepages.OfflinePageUtils;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.pageinfo.WebsiteSettingsPopup;
@@ -693,6 +694,12 @@ public abstract class ChromeActivity extends AsyncInitializationActivity
mToolbarManager.onDeferredStartup(getOnCreateTimestampMs(), simpleName);
}
recordKeyboardLocaleUma();
+
+ // TODO(treib): Remove this when we have the proper morning reads fetching logic in place
+ if (CommandLine.getInstance().hasSwitch(ChromeSwitches.ENABLE_NTP_SNIPPETS)) {
+ // Initialize snippets
+ SnippetsController.get(this).fetchSnippets(false);
+ }
}
@Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
index b7b1237..f01a0f9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPage.java
@@ -47,6 +47,7 @@ import org.chromium.chrome.browser.ntp.LogoBridge.LogoObserver;
import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
import org.chromium.chrome.browser.ntp.interests.InterestsPage;
import org.chromium.chrome.browser.ntp.interests.InterestsPage.InterestsClickListener;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsManager;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.preferences.DocumentModeManager;
import org.chromium.chrome.browser.preferences.DocumentModePreference;
@@ -120,6 +121,7 @@ public class NewTabPage
private String mAnimatedLogoUrl;
private FakeboxDelegate mFakeboxDelegate;
private OfflinePageBridge mOfflinePageBridge;
+ private SnippetsManager mSnippetsManager;
// The timestamp at which the constructor was called.
private final long mConstructedTimeNs;
@@ -587,10 +589,12 @@ public class NewTabPage
mLogoBridge = new LogoBridge(mProfile);
updateSearchProviderHasLogo();
+ mSnippetsManager = new SnippetsManager(mNewTabPageManager, mProfile);
+
LayoutInflater inflater = LayoutInflater.from(activity);
mNewTabPageView = (NewTabPageView) inflater.inflate(R.layout.new_tab_page, null);
mNewTabPageView.initialize(mNewTabPageManager, isInSingleUrlBarMode(activity),
- mSearchProviderHasLogo);
+ mSearchProviderHasLogo, mSnippetsManager);
mIsTablet = DeviceFormFactor.isTablet(activity);
@@ -772,6 +776,10 @@ public class NewTabPage
mLogoBridge.destroy();
mLogoBridge = null;
}
+ if (mSnippetsManager != null) {
+ mSnippetsManager.destroy();
+ mSnippetsManager = null;
+ }
if (mMostVisitedItemRemovedController != null) {
mTab.getSnackbarManager().dismissSnackbars(mMostVisitedItemRemovedController);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
index 797d04d..43c35c7 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/NewTabPageView.java
@@ -87,7 +87,6 @@ public class NewTabPageView extends FrameLayout
private NewTabPageManager mManager;
private MostVisitedDesign mMostVisitedDesign;
private MostVisitedItem[] mMostVisitedItems;
- private SnippetsManager mSnippetsManager;
private boolean mFirstShow = true;
private boolean mSearchProviderHasLogo = true;
private boolean mHasReceivedMostVisitedSites;
@@ -250,7 +249,7 @@ public class NewTabPageView extends FrameLayout
* @param searchProviderHasLogo Whether the search provider has a logo.
*/
public void initialize(NewTabPageManager manager, boolean isSingleUrlBarMode,
- boolean searchProviderHasLogo) {
+ boolean searchProviderHasLogo, SnippetsManager snippetsManager) {
mManager = manager;
mScrollView = (NewTabScrollView) findViewById(R.id.ntp_scrollview);
@@ -340,7 +339,6 @@ public class NewTabPageView extends FrameLayout
RecordHistogram.recordEnumeratedHistogram(SnippetsManager.SNIPPETS_STATE_HISTOGRAM,
SnippetsManager.SNIPPETS_SHOWN, SnippetsManager.NUM_SNIPPETS_ACTIONS);
mSnippetsView.setLayoutManager(new LinearLayoutManager(getContext()));
- mSnippetsManager = new SnippetsManager(mManager, mSnippetsView);
mSnippetsView.addOnScrollListener(new RecyclerView.OnScrollListener() {
private boolean mScrolledOnce = false;
@Override
@@ -355,6 +353,7 @@ public class NewTabPageView extends FrameLayout
SnippetsManager.NUM_SNIPPETS_ACTIONS);
}
});
+ snippetsManager.setSnippetsView(mSnippetsView);
}
// Set up interests
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetCardItemViewHolder.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetCardItemViewHolder.java
index 14adb83..d032d52 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetCardItemViewHolder.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetCardItemViewHolder.java
@@ -102,7 +102,7 @@ class SnippetCardItemViewHolder extends SnippetListItemViewHolder implements Vie
mHeadlineTextView.setText(item.mTitle);
mPublisherTextView.setText(item.mPublisher);
- mArticleSnippetTextView.setText(item.mSnippet);
+ mArticleSnippetTextView.setText(item.mPreviewText);
mUrl = item.mUrl;
mPosition = item.mPosition;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
new file mode 100644
index 0000000..db0b5ad
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java
@@ -0,0 +1,54 @@
+// Copyright 2016 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.ntp.snippets;
+
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.chrome.browser.profiles.Profile;
+
+/**
+ * Provides access to the snippets to display on the NTP using the C++ NTP Snippets Service
+ */
+public class SnippetsBridge {
+ private static final String TAG = "SnippetsBridge";
+
+ private long mNativeSnippetsBridge;
+
+ /**
+ * An observer for notifying when new snippets are loaded
+ */
+ public interface SnippetsObserver {
+ @CalledByNative("SnippetsObserver")
+ public void onSnippetsAvailable(
+ String[] titles, String[] urls, String[] thumbnailUrls, String[] previewText);
+ }
+
+ /**
+ * Creates a SnippetsBridge for getting snippet data for the current user
+ *
+ * @param profile Profile of the user that we will retrieve snippets for.
+ */
+ public SnippetsBridge(Profile profile, final SnippetsObserver observer) {
+ SnippetsObserver wrappedObserver = new SnippetsObserver() {
+ @Override
+ public void onSnippetsAvailable(
+ String[] titles, String[] urls, String[] thumbnailUrls, String[] previewText) {
+ // Don't notify observer if we've already been destroyed.
+ if (mNativeSnippetsBridge != 0) {
+ observer.onSnippetsAvailable(titles, urls, thumbnailUrls, previewText);
+ }
+ }
+ };
+ mNativeSnippetsBridge = nativeInit(profile, wrappedObserver);
+ }
+
+ void destroy() {
+ assert mNativeSnippetsBridge != 0;
+ nativeDestroy(mNativeSnippetsBridge);
+ mNativeSnippetsBridge = 0;
+ }
+
+ private native long nativeInit(Profile profile, SnippetsObserver observer);
+ private native void nativeDestroy(long nativeNTPSnippetsBridge);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsController.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsController.java
new file mode 100644
index 0000000..55dd84c
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsController.java
@@ -0,0 +1,59 @@
+// Copyright 2016 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.ntp.snippets;
+
+import android.content.Context;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.chrome.browser.profiles.Profile;
+import org.chromium.chrome.browser.signin.SigninManager;
+import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
+
+/**
+ * The main controller for calling into the native snippets component to fetch snippets
+ */
+public class SnippetsController implements SignInStateObserver {
+ private static SnippetsController sInstance;
+
+ private long mNativeSnippetsController;
+
+ public SnippetsController(Context applicationContext) {
+ SigninManager.get(applicationContext).addSignInStateObserver(this);
+ }
+
+ /**
+ * Fetches new snippets
+ *
+ * @param overwrite true if an update to the current snippets should be forced, and false if
+ * snippets should be downloaded only if there are no existing ones.
+ */
+ public void fetchSnippets(boolean overwrite) {
+ nativeFetchSnippets(Profile.getLastUsedProfile(), overwrite);
+ }
+
+ /**
+ * Retrieve the singleton instance of this class.
+ *
+ * @param context the current context.
+ * @return the singleton instance.
+ */
+ public static SnippetsController get(Context context) {
+ ThreadUtils.assertOnUiThread();
+ if (sInstance == null) {
+ sInstance = new SnippetsController(context.getApplicationContext());
+ }
+ return sInstance;
+ }
+
+ @Override
+ public void onSignedIn() {
+ fetchSnippets(true);
+ }
+
+ @Override
+ public void onSignedOut() {}
+
+ private native void nativeFetchSnippets(Profile profile, boolean overwrite);
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsManager.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsManager.java
index eb90180..7faf17a 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsManager.java
@@ -6,21 +6,19 @@ package org.chromium.chrome.browser.ntp.snippets;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.os.Environment;
import android.support.v7.widget.RecyclerView;
-import android.util.JsonReader;
import android.widget.ImageView;
import org.chromium.base.Log;
import org.chromium.base.StreamUtil;
-import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.ntp.NewTabPageView.NewTabPageManager;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge.SnippetsObserver;
+import org.chromium.chrome.browser.profiles.Profile;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -51,6 +49,7 @@ public class SnippetsManager {
private NewTabPageManager mNewTabPageManager;
private SnippetsAdapter mDataAdapter;
+ private SnippetsBridge mSnippetsBridge;
/** Base type for anything to add to the snippets view
*/
@@ -86,15 +85,15 @@ public class SnippetsManager {
public static class SnippetArticle implements SnippetListItem {
public final String mTitle;
public final String mPublisher;
- public final String mSnippet;
+ public final String mPreviewText;
public final String mUrl;
public final String mThumbnailPath;
public final int mPosition;
private ThumbnailRenderingTask mThumbnailRenderingTask;
- // Async task to create the thumbnail from a local file
- // TODO(maybelle): This task to retrieve the thumbnail from local disk is temporary while
+ // Async task to create the thumbnail from a URL
+ // TODO(maybelle): This task to retrieve the thumbnail from the web is temporary while
// we are prototyping this feature for clank. For the real production feature, we
// will likely have to download/decode the thumbnail on the native side.
private static class ThumbnailRenderingTask extends AsyncTask<String, Void, Drawable> {
@@ -106,8 +105,18 @@ public class SnippetsManager {
@Override
protected Drawable doInBackground(String... params) {
- String thumbnailPath = params[0];
- return Drawable.createFromPath(thumbnailPath);
+ InputStream is = null;
+ try {
+ is = (InputStream) new URL(params[0]).getContent();
+ return Drawable.createFromStream(is, "thumbnail");
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Could not get image thumbnail due to malformed URL", e);
+ } catch (IOException e) {
+ Log.e(TAG, "Could not get image thumbnail", e);
+ } finally {
+ StreamUtil.closeQuietly(is);
+ }
+ return null;
}
@Override
@@ -116,11 +125,11 @@ public class SnippetsManager {
}
}
- public SnippetArticle(String title, String publisher, String snippet, String url,
+ public SnippetArticle(String title, String publisher, String previewText, String url,
String thumbnailPath, int position) {
mTitle = title;
mPublisher = publisher;
- mSnippet = snippet;
+ mPreviewText = previewText;
mUrl = url;
mThumbnailPath = thumbnailPath;
mPosition = position;
@@ -146,117 +155,33 @@ public class SnippetsManager {
}
}
- private class ReadFileTask extends AsyncTask<Void, Void, List<SnippetListItem>> {
- private int mNumArticles;
-
- @Override
- protected List<SnippetListItem> doInBackground(Void... params) {
- FileInputStream fis = null;
- try {
- fis = new FileInputStream(
- new File(Environment.getExternalStorageDirectory().getPath()
- + "/chrome/reading_list.json"));
- List<SnippetListItem> listSnippetsGroups = readJsonStream(fis);
- return listSnippetsGroups;
- } catch (IOException ex) {
- Log.e(TAG, "Exception reading file: %s ", ex);
- } finally {
- StreamUtil.closeQuietly(fis);
- }
- return null;
- }
-
- @Override
- protected void onPostExecute(List<SnippetListItem> listSnippetsGroups) {
- if (listSnippetsGroups == null) return;
-
- mDataAdapter.setSnippetListItems(listSnippetsGroups);
- }
-
- private List<SnippetListItem> readJsonStream(InputStream in) throws IOException {
- JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
- try {
- return readRecommendationsArray(reader);
- } finally {
- reader.close();
- }
- }
-
- private List<SnippetListItem> readRecommendationsArray(JsonReader reader)
- throws IOException {
- List<SnippetListItem> listSnippetItems = new ArrayList<SnippetListItem>();
- mNumArticles = 0;
- reader.beginArray();
- while (reader.hasNext()) {
- readSnippetGroup(listSnippetItems, reader);
- }
- reader.endArray();
- RecordHistogram.recordSparseSlowlyHistogram(
- "NewTabPage.Snippets.NumArticles", mNumArticles);
- return listSnippetItems;
- }
-
- private void readSnippetGroup(List<SnippetListItem> listSnippetItems, JsonReader reader)
- throws IOException {
- reader.beginObject();
- while (reader.hasNext()) {
- String name = reader.nextName();
- if (name.equals("recommendation_basis")) {
- listSnippetItems.add(new SnippetHeader(reader.nextString()));
- } else if (name.equals("articles")) {
- readArticlesArray(listSnippetItems, reader);
- }
- }
- reader.endObject();
- }
-
- private void readArticlesArray(List<SnippetListItem> listSnippetItems, JsonReader reader)
- throws IOException {
- reader.beginArray();
- while (reader.hasNext()) {
- listSnippetItems.add(readArticleDetails(reader));
- }
- reader.endArray();
- }
-
- private SnippetArticle readArticleDetails(JsonReader reader) throws IOException {
- String headline = "";
- String publisher = "";
- String snippets = "";
- String url = "";
- String thumbnail = "";
-
- reader.beginObject();
- while (reader.hasNext()) {
- String name = reader.nextName();
- if (name.equals("headline")) {
- headline = reader.nextString();
- } else if (name.equals("publisher")) {
- publisher = reader.nextString();
- } else if (name.equals("snippet")) {
- snippets = reader.nextString();
- } else if (name.equals("url")) {
- url = reader.nextString();
- } else if (name.equals("thumbnail")) {
- thumbnail = reader.nextString();
- } else {
- reader.skipValue();
+ public SnippetsManager(NewTabPageManager tabManager, Profile profile) {
+ mNewTabPageManager = tabManager;
+ mDataAdapter = new SnippetsAdapter(this);
+ mSnippetsBridge = new SnippetsBridge(profile, new SnippetsObserver() {
+ @Override
+ public void onSnippetsAvailable(
+ String[] titles, String[] urls, String[] thumbnailUrls, String[] previewText) {
+ List<SnippetListItem> listItems = new ArrayList<SnippetListItem>();
+ for (int i = 0; i < titles.length; ++i) {
+ SnippetArticle article = new SnippetArticle(
+ titles[i], "", previewText[i], urls[i], thumbnailUrls[i], i);
+ listItems.add(article);
}
+ mDataAdapter.setSnippetListItems(listItems);
}
- reader.endObject();
- return new SnippetsManager.SnippetArticle(
- headline, publisher, snippets, url, thumbnail, mNumArticles++);
- }
+ });
}
- public SnippetsManager(NewTabPageManager tabManager, RecyclerView mSnippetsView) {
- mNewTabPageManager = tabManager;
- mDataAdapter = new SnippetsAdapter(this);
+ public void setSnippetsView(RecyclerView mSnippetsView) {
mSnippetsView.setAdapter(mDataAdapter);
- new ReadFileTask().execute();
}
public void loadUrl(String url) {
mNewTabPageManager.open(url);
}
+
+ public void destroy() {
+ mSnippetsBridge.destroy();
+ }
}
diff --git a/chrome/browser/android/DEPS b/chrome/browser/android/DEPS
index 5894717..4fc151f 100644
--- a/chrome/browser/android/DEPS
+++ b/chrome/browser/android/DEPS
@@ -2,6 +2,7 @@ include_rules = [
"-components/devtools_bridge",
"+components/devtools_discovery",
"+components/devtools_http_handler",
+ "+components/ntp_snippets",
"+components/service_tab_launcher",
"+components/toolbar",
"+components/web_contents_delegate_android",
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index d52c26b..53305a1 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -60,6 +60,8 @@
#include "chrome/browser/android/most_visited_sites.h"
#include "chrome/browser/android/net/external_estimate_provider_android.h"
#include "chrome/browser/android/new_tab_page_prefs.h"
+#include "chrome/browser/android/ntp_snippets_bridge.h"
+#include "chrome/browser/android/ntp_snippets_controller.h"
#include "chrome/browser/android/offline_pages/offline_page_bridge.h"
#include "chrome/browser/android/omnibox/answers_image_bridge.h"
#include "chrome/browser/android/omnibox/autocomplete_controller_android.h"
@@ -305,6 +307,8 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
{"NewTabPagePrefs", NewTabPagePrefs::RegisterNewTabPagePrefs},
{"NotificationUIManager",
NotificationUIManagerAndroid::RegisterNotificationUIManager},
+ {"NTPSnippetsBridge", NTPSnippetsBridge::Register},
+ {"NTPSnippetsController", NTPSnippetsController::Register},
{"OAuth2TokenServiceDelegateAndroid",
OAuth2TokenServiceDelegateAndroid::Register},
{"OfflinePageBridge", offline_pages::android::RegisterOfflinePageBridge},
diff --git a/chrome/browser/android/ntp_snippets_bridge.cc b/chrome/browser/android/ntp_snippets_bridge.cc
new file mode 100644
index 0000000..1613ae42
--- /dev/null
+++ b/chrome/browser/android/ntp_snippets_bridge.cc
@@ -0,0 +1,84 @@
+// Copyright 2016 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/ntp_snippets_bridge.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/scoped_java_ref.h"
+#include "chrome/browser/ntp_snippets/ntp_snippets_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_android.h"
+#include "components/ntp_snippets/ntp_snippet.h"
+#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "jni/SnippetsBridge_jni.h"
+
+using base::android::JavaParamRef;
+using base::android::ToJavaArrayOfStrings;
+using ntp_snippets::NTPSnippetsService;
+using ntp_snippets::NTPSnippetsServiceObserver;
+
+static jlong Init(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ const JavaParamRef<jobject>& j_profile,
+ const JavaParamRef<jobject>& j_observer) {
+ NTPSnippetsBridge* snippets_bridge =
+ new NTPSnippetsBridge(env, j_profile, j_observer);
+ return reinterpret_cast<intptr_t>(snippets_bridge);
+}
+
+NTPSnippetsBridge::NTPSnippetsBridge(JNIEnv* env,
+ jobject j_profile,
+ jobject j_observer)
+ : snippet_service_observer_(this) {
+ observer_.Reset(env, j_observer);
+
+ Profile* profile = ProfileAndroid::FromProfileAndroid(j_profile);
+ ntp_snippets_service_ = NTPSnippetsServiceFactory::GetForProfile(profile);
+ snippet_service_observer_.Add(ntp_snippets_service_);
+ if (ntp_snippets_service_->is_loaded())
+ NTPSnippetsServiceLoaded(ntp_snippets_service_);
+}
+
+NTPSnippetsBridge::~NTPSnippetsBridge() {}
+
+void NTPSnippetsBridge::Destroy(JNIEnv* env, const JavaParamRef<jobject>& obj) {
+ delete this;
+}
+
+void NTPSnippetsBridge::NTPSnippetsServiceLoaded(NTPSnippetsService* service) {
+ if (observer_.is_null())
+ return;
+
+ std::vector<std::string> titles;
+ std::vector<std::string> urls;
+ std::vector<std::string> thumbnail_urls;
+ std::vector<std::string> snippets;
+ for (const ntp_snippets::NTPSnippet& snippet : *service) {
+ titles.push_back(snippet.title());
+ urls.push_back(snippet.url().spec());
+ thumbnail_urls.push_back(snippet.salient_image_url().spec());
+ snippets.push_back(snippet.snippet());
+ }
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_SnippetsObserver_onSnippetsAvailable(
+ env, observer_.obj(), ToJavaArrayOfStrings(env, titles).obj(),
+ ToJavaArrayOfStrings(env, urls).obj(),
+ ToJavaArrayOfStrings(env, thumbnail_urls).obj(),
+ ToJavaArrayOfStrings(env, snippets).obj());
+}
+
+void NTPSnippetsBridge::NTPSnippetsServiceShutdown(
+ NTPSnippetsService* service) {
+ observer_.Reset();
+ snippet_service_observer_.Remove(ntp_snippets_service_);
+}
+
+// static
+bool NTPSnippetsBridge::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
diff --git a/chrome/browser/android/ntp_snippets_bridge.h b/chrome/browser/android/ntp_snippets_bridge.h
new file mode 100644
index 0000000..727cd27
--- /dev/null
+++ b/chrome/browser/android/ntp_snippets_bridge.h
@@ -0,0 +1,44 @@
+// Copyright 2016 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_NTP_SNIPPETS_BRIDGE_H_
+#define CHROME_BROWSER_ANDROID_NTP_SNIPPETS_BRIDGE_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+#include "base/scoped_observer.h"
+#include "components/ntp_snippets/ntp_snippets_service.h"
+
+// The C++ counterpart to NTPSnippetsBridge.java. Enables Java code to access
+// the list of snippets to show on the NTP
+class NTPSnippetsBridge : public ntp_snippets::NTPSnippetsServiceObserver {
+ public:
+ explicit NTPSnippetsBridge(JNIEnv* env,
+ jobject j_profile,
+ jobject j_observer);
+ void Destroy(JNIEnv* env, const base::android::JavaParamRef<jobject>& obj);
+
+ static bool Register(JNIEnv* env);
+
+ private:
+ ~NTPSnippetsBridge() override;
+
+ // NTPSnippetsServiceObserver overrides
+ void NTPSnippetsServiceLoaded(
+ ntp_snippets::NTPSnippetsService* service) override;
+ void NTPSnippetsServiceShutdown(
+ ntp_snippets::NTPSnippetsService* service) override;
+
+ ntp_snippets::NTPSnippetsService* ntp_snippets_service_;
+
+ base::android::ScopedJavaGlobalRef<jobject> observer_;
+ ScopedObserver<ntp_snippets::NTPSnippetsService,
+ ntp_snippets::NTPSnippetsServiceObserver>
+ snippet_service_observer_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsBridge);
+};
+
+#endif // CHROME_BROWSER_ANDROID_NTP_SNIPPETS_BRIDGE_H_
diff --git a/chrome/browser/android/ntp_snippets_controller.cc b/chrome/browser/android/ntp_snippets_controller.cc
new file mode 100644
index 0000000..a252d52
--- /dev/null
+++ b/chrome/browser/android/ntp_snippets_controller.cc
@@ -0,0 +1,34 @@
+// Copyright 2016 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/ntp_snippets_controller.h"
+
+#include <jni.h>
+
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "chrome/browser/ntp_snippets/ntp_snippets_service_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_android.h"
+#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "jni/SnippetsController_jni.h"
+
+using base::android::JavaParamRef;
+
+static void FetchSnippets(JNIEnv* env,
+ const JavaParamRef<jobject>& obj,
+ const JavaParamRef<jobject>& jprofile,
+ jboolean overwrite) {
+ Profile* profile = ProfileAndroid::FromProfileAndroid(jprofile);
+ if (profile) {
+ ntp_snippets::NTPSnippetsService* ntp_snippets_service =
+ NTPSnippetsServiceFactory::GetForProfile(profile);
+ ntp_snippets_service->FetchSnippets(overwrite);
+ }
+}
+
+// static
+bool NTPSnippetsController::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
diff --git a/chrome/browser/android/ntp_snippets_controller.h b/chrome/browser/android/ntp_snippets_controller.h
new file mode 100644
index 0000000..d5bc137
--- /dev/null
+++ b/chrome/browser/android/ntp_snippets_controller.h
@@ -0,0 +1,21 @@
+// Copyright 2016 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_NTP_SNIPPETS_CONTROLLER_H_
+#define CHROME_BROWSER_ANDROID_NTP_SNIPPETS_CONTROLLER_H_
+
+#include <jni.h>
+
+#include "base/android/scoped_java_ref.h"
+
+// The C++ counterpart to SnippetsController.java. Enables Java code to fetch
+// snippets from the server
+class NTPSnippetsController {
+ public:
+ static bool Register(JNIEnv* env);
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsController);
+};
+
+#endif // CHROME_BROWSER_ANDROID_NTP_SNIPPETS_CONTROLLER_H_
diff --git a/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc b/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc
index 5730e2f..0cf2dba 100644
--- a/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc
+++ b/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc
@@ -8,9 +8,18 @@
#include "base/memory/singleton.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/ntp_snippets/ntp_snippets_fetcher.h"
#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "components/signin/core/browser/signin_manager.h"
#include "content/public/browser/browser_context.h"
+#include "content/public/browser/browser_thread.h"
+#include "net/url_request/url_request_context_getter.h"
+
+using content::BrowserThread;
// static
NTPSnippetsServiceFactory* NTPSnippetsServiceFactory::GetInstance() {
@@ -34,6 +43,23 @@ NTPSnippetsServiceFactory::~NTPSnippetsServiceFactory() {}
KeyedService* NTPSnippetsServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
+ Profile* profile = Profile::FromBrowserContext(context);
+ SigninManagerBase* signin_manager =
+ SigninManagerFactory::GetForProfile(profile);
+ OAuth2TokenService* token_service =
+ ProfileOAuth2TokenServiceFactory::GetForProfile(profile);
+ scoped_refptr<net::URLRequestContextGetter> request_context =
+ context->GetRequestContext();
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ BrowserThread::GetBlockingPool()
+ ->GetSequencedTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::GetSequenceToken(),
+ base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
+
return new ntp_snippets::NTPSnippetsService(
- g_browser_process->GetApplicationLocale());
+ task_runner, g_browser_process->GetApplicationLocale(),
+ make_scoped_ptr(new ntp_snippets::NTPSnippetsFetcher(
+ task_runner, signin_manager, token_service, request_context,
+ profile->GetPath())));
}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index fd35607..fae5844 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -831,6 +831,10 @@
'browser/android/new_tab_page_prefs.h',
'browser/android/new_tab_page_url_handler.cc',
'browser/android/new_tab_page_url_handler.h',
+ 'browser/android/ntp_snippets_bridge.cc',
+ 'browser/android/ntp_snippets_bridge.h',
+ 'browser/android/ntp_snippets_controller.cc',
+ 'browser/android/ntp_snippets_controller.h',
'browser/android/omnibox/answers_image_bridge.cc',
'browser/android/omnibox/answers_image_bridge.h',
'browser/android/omnibox/autocomplete_controller_android.cc',
@@ -1877,10 +1881,12 @@
'android/java/src/org/chromium/chrome/browser/net/spdyproxy/DataReductionProxySettings.java',
'android/java/src/org/chromium/chrome/browser/notifications/NotificationUIManager.java',
'android/java/src/org/chromium/chrome/browser/ntp/ForeignSessionHelper.java',
+ 'android/java/src/org/chromium/chrome/browser/ntp/interests/InterestsService.java',
'android/java/src/org/chromium/chrome/browser/ntp/LogoBridge.java',
'android/java/src/org/chromium/chrome/browser/ntp/NewTabPagePrefs.java',
'android/java/src/org/chromium/chrome/browser/ntp/RecentlyClosedBridge.java',
- 'android/java/src/org/chromium/chrome/browser/ntp/interests/InterestsService.java',
+ 'android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsBridge.java',
+ 'android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsController.java',
'android/java/src/org/chromium/chrome/browser/offlinepages/OfflinePageBridge.java',
'android/java/src/org/chromium/chrome/browser/omnibox/AnswersImage.java',
'android/java/src/org/chromium/chrome/browser/omnibox/AutocompleteController.java',
diff --git a/components/BUILD.gn b/components/BUILD.gn
index 3e17b8d..4fc532d 100644
--- a/components/BUILD.gn
+++ b/components/BUILD.gn
@@ -51,6 +51,7 @@ test("components_unittests") {
"//components/metrics:unit_tests",
"//components/net_log:unit_tests",
"//components/network_time:unit_tests",
+ "//components/ntp_snippets:unit_tests",
"//components/open_from_clipboard:unit_tests",
"//components/os_crypt:unit_tests",
"//components/prefs:unit_tests",
diff --git a/components/ntp_snippets.gypi b/components/ntp_snippets.gypi
index e3267dc..551d3e6 100644
--- a/components/ntp_snippets.gypi
+++ b/components/ntp_snippets.gypi
@@ -20,6 +20,8 @@
'ntp_snippets/inner_iterator.h',
'ntp_snippets/ntp_snippet.cc',
'ntp_snippets/ntp_snippet.h',
+ 'ntp_snippets/ntp_snippets_fetcher.cc',
+ 'ntp_snippets/ntp_snippets_fetcher.h',
'ntp_snippets/ntp_snippets_service.cc',
'ntp_snippets/ntp_snippets_service.h',
],
diff --git a/components/ntp_snippets/BUILD.gn b/components/ntp_snippets/BUILD.gn
index 89e0ccd..222ad18 100644
--- a/components/ntp_snippets/BUILD.gn
+++ b/components/ntp_snippets/BUILD.gn
@@ -8,6 +8,8 @@ source_set("ntp_snippets") {
"inner_iterator.h",
"ntp_snippet.cc",
"ntp_snippet.h",
+ "ntp_snippets_fetcher.cc",
+ "ntp_snippets_fetcher.h",
"ntp_snippets_service.cc",
"ntp_snippets_service.h",
]
@@ -15,6 +17,9 @@ source_set("ntp_snippets") {
public_deps = [
"//base",
"//components/keyed_service/core",
+ "//components/signin/core/browser",
+ "//google_apis",
+ "//net",
"//url",
]
}
@@ -28,6 +33,9 @@ source_set("unit_tests") {
deps = [
":ntp_snippets",
+ "//base",
+ "//components/signin/core/browser:test_support",
+ "//net:test_support",
"//testing/gtest",
]
}
diff --git a/components/ntp_snippets/DEPS b/components/ntp_snippets/DEPS
index f0bf3d9..830c20f 100644
--- a/components/ntp_snippets/DEPS
+++ b/components/ntp_snippets/DEPS
@@ -1,3 +1,8 @@
include_rules = [
"+components/keyed_service/core",
+ "+components/signin",
+ "+net/base",
+ "+net/http",
+ "+net/url_request",
+ "+google_apis/gaia",
]
diff --git a/components/ntp_snippets/ntp_snippet.cc b/components/ntp_snippets/ntp_snippet.cc
index ef59e1b..9ee2029 100644
--- a/components/ntp_snippets/ntp_snippet.cc
+++ b/components/ntp_snippets/ntp_snippet.cc
@@ -14,14 +14,14 @@ NTPSnippet::NTPSnippet(const GURL& url) : url_(url) {
NTPSnippet::~NTPSnippet() {}
// static
-std::unique_ptr<NTPSnippet> NTPSnippet::NTPSnippetFromDictionary(
+scoped_ptr<NTPSnippet> NTPSnippet::NTPSnippetFromDictionary(
const base::DictionaryValue& dict) {
// Need at least the url.
std::string url;
if (!dict.GetString("url", &url))
return nullptr;
- std::unique_ptr<NTPSnippet> snippet(new NTPSnippet(GURL(url)));
+ scoped_ptr<NTPSnippet> snippet(new NTPSnippet(GURL(url)));
std::string site_title;
if (dict.GetString("site_title", &site_title))
diff --git a/components/ntp_snippets/ntp_snippet.h b/components/ntp_snippets/ntp_snippet.h
index 6ea126a..6dd2ad04 100644
--- a/components/ntp_snippets/ntp_snippet.h
+++ b/components/ntp_snippets/ntp_snippet.h
@@ -4,7 +4,6 @@
#ifndef COMPONENTS_NTP_SNIPPETS_NTP_SNIPPET_H_
#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPET_H_
-#include <memory>
#include <string>
#include "base/macros.h"
@@ -30,7 +29,7 @@ class NTPSnippet {
// dictionary doesn't contain at least a url. The keys in the dictionary are
// expected to be the same as the property name, with exceptions documented in
// the property comment.
- static std::unique_ptr<NTPSnippet> NTPSnippetFromDictionary(
+ static scoped_ptr<NTPSnippet> NTPSnippetFromDictionary(
const base::DictionaryValue& dict);
// URL of the page described by this snippet.
diff --git a/components/ntp_snippets/ntp_snippets_fetcher.cc b/components/ntp_snippets/ntp_snippets_fetcher.cc
new file mode 100644
index 0000000..7c74fcf
--- /dev/null
+++ b/components/ntp_snippets/ntp_snippets_fetcher.cc
@@ -0,0 +1,191 @@
+// Copyright 2016 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 "components/ntp_snippets/ntp_snippets_fetcher.h"
+
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/task_runner_util.h"
+#include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "components/signin/core/browser/signin_manager.h"
+#include "components/signin/core/browser/signin_tracker.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/url_request/url_fetcher.h"
+
+using net::URLFetcher;
+using net::URLRequestContextGetter;
+using net::HttpRequestHeaders;
+using net::URLRequestStatus;
+
+namespace ntp_snippets {
+
+const char kSnippetSuggestionsFilename[] = "ntp_snippets.json";
+const char kApiScope[] = "https://www.googleapis.com/auth/webhistory";
+const char kContentSnippetsServer[] =
+ "https://chromereader-pa.googleapis.com/v1/fetch";
+const char kAuthorizationRequestHeaderFormat[] = "Bearer %s";
+
+const char kUnpersonalizedRequestParameters[] =
+ "{ \"response_detail_level\": \"FULL_DEBUG\", \"advanced_options\": { "
+ "\"local_scoring_params\": {\"content_params\" : { "
+ "\"only_return_personalized_results\": false } }, "
+ "\"global_scoring_params\": { \"num_to_return\": 10 } } }";
+
+base::FilePath GetSnippetsSuggestionsPath(const base::FilePath& base_dir) {
+ return base_dir.AppendASCII(kSnippetSuggestionsFilename);
+}
+
+NTPSnippetsFetcher::NTPSnippetsFetcher(
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ SigninManagerBase* signin_manager,
+ OAuth2TokenService* token_service,
+ scoped_refptr<URLRequestContextGetter> url_request_context_getter,
+ const base::FilePath& base_download_path)
+ : OAuth2TokenService::Consumer("NTP_snippets"),
+ file_task_runner_(file_task_runner),
+ url_request_context_getter_(url_request_context_getter),
+ signin_manager_(signin_manager),
+ token_service_(token_service),
+ download_path_(GetSnippetsSuggestionsPath(base_download_path)),
+ waiting_for_refresh_token_(false),
+ weak_ptr_factory_(this) {}
+
+NTPSnippetsFetcher::~NTPSnippetsFetcher() {
+ if (waiting_for_refresh_token_)
+ token_service_->RemoveObserver(this);
+}
+
+scoped_ptr<NTPSnippetsFetcher::SnippetsAvailableCallbackList::Subscription>
+NTPSnippetsFetcher::AddCallback(const SnippetsAvailableCallback& callback) {
+ return callback_list_.Add(callback);
+}
+
+void NTPSnippetsFetcher::FetchSnippets(bool overwrite) {
+ if (overwrite) {
+ StartFetch();
+ } else {
+ base::PostTaskAndReplyWithResult(
+ file_task_runner_.get(), FROM_HERE,
+ base::Bind(&base::PathExists, download_path_),
+ base::Bind(&NTPSnippetsFetcher::OnFileExistsCheckDone,
+ weak_ptr_factory_.GetWeakPtr()));
+ }
+}
+
+void NTPSnippetsFetcher::OnFileExistsCheckDone(bool exists) {
+ if (exists) {
+ NotifyObservers();
+ } else {
+ StartFetch();
+ }
+}
+
+void NTPSnippetsFetcher::StartFetch() {
+ if (signin_manager_->IsAuthenticated()) {
+ StartTokenRequest();
+ } else {
+ if (!waiting_for_refresh_token_) {
+ // Wait until we get a refresh token.
+ waiting_for_refresh_token_ = true;
+ token_service_->AddObserver(this);
+ }
+ }
+}
+
+void NTPSnippetsFetcher::StartTokenRequest() {
+ OAuth2TokenService::ScopeSet scopes;
+ scopes.insert(kApiScope);
+ oauth_request_ = token_service_->StartRequest(
+ signin_manager_->GetAuthenticatedAccountId(), scopes, this);
+}
+
+void NTPSnippetsFetcher::NotifyObservers() {
+ callback_list_.Notify(download_path_);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OAuth2TokenService::Consumer overrides
+void NTPSnippetsFetcher::OnGetTokenSuccess(
+ const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) {
+ oauth_request_.reset();
+ url_fetcher_ =
+ URLFetcher::Create(GURL(kContentSnippetsServer), URLFetcher::POST, this);
+ url_fetcher_->SetRequestContext(url_request_context_getter_.get());
+ url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES);
+ HttpRequestHeaders headers;
+ headers.SetHeader("Authorization",
+ base::StringPrintf(kAuthorizationRequestHeaderFormat,
+ access_token.c_str()));
+ headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
+ url_fetcher_->SetExtraRequestHeaders(headers.ToString());
+ url_fetcher_->SetUploadData("application/json",
+ kUnpersonalizedRequestParameters);
+ url_fetcher_->SaveResponseToTemporaryFile(file_task_runner_.get());
+ url_fetcher_->Start();
+}
+
+void NTPSnippetsFetcher::OnGetTokenFailure(
+ const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) {
+ oauth_request_.reset();
+ DLOG(ERROR) << "Unable to get token: " << error.ToString();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// OAuth2TokenService::Observer overrides
+void NTPSnippetsFetcher::OnRefreshTokenAvailable(
+ const std::string& account_id) {
+ token_service_->RemoveObserver(this);
+ waiting_for_refresh_token_ = false;
+ StartTokenRequest();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// URLFetcherDelegate overrides
+void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) {
+ DCHECK_EQ(url_fetcher_.get(), source);
+
+ const URLRequestStatus& status = source->GetStatus();
+ if (!status.is_success()) {
+ DLOG(WARNING) << "URLRequestStatus error " << status.error()
+ << " while trying to download " << source->GetURL().spec();
+ return;
+ }
+
+ int response_code = source->GetResponseCode();
+ if (response_code != net::HTTP_OK) {
+ DLOG(WARNING) << "HTTP error " << response_code
+ << " while trying to download " << source->GetURL().spec();
+ return;
+ }
+
+ base::FilePath response_path;
+ source->GetResponseAsFilePath(false, &response_path);
+
+ base::PostTaskAndReplyWithResult(
+ file_task_runner_.get(), FROM_HERE,
+ base::Bind(&base::Move, response_path, download_path_),
+ base::Bind(&NTPSnippetsFetcher::OnFileMoveDone,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void NTPSnippetsFetcher::OnFileMoveDone(bool success) {
+ if (!success) {
+ DLOG(WARNING) << "Could not move file to "
+ << download_path_.LossyDisplayName();
+ return;
+ }
+
+ NotifyObservers();
+}
+
+} // namespace ntp_snippets
diff --git a/components/ntp_snippets/ntp_snippets_fetcher.h b/components/ntp_snippets/ntp_snippets_fetcher.h
new file mode 100644
index 0000000..2ae47ad
--- /dev/null
+++ b/components/ntp_snippets/ntp_snippets_fetcher.h
@@ -0,0 +1,94 @@
+// Copyright 2016 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 COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_FETCHER_H_
+#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_FETCHER_H_
+
+#include "base/callback.h"
+#include "base/callback_list.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "base/observer_list.h"
+#include "base/sequenced_task_runner.h"
+#include "google_apis/gaia/oauth2_token_service.h"
+#include "net/url_request/url_request_context_getter.h"
+
+namespace net {
+class URLFetcher;
+class URLFetcherDelegate;
+} // namespace net
+
+class SigninManagerBase;
+
+namespace ntp_snippets {
+
+// Fetches snippet data for the NTP from the server
+class NTPSnippetsFetcher : public OAuth2TokenService::Consumer,
+ public OAuth2TokenService::Observer,
+ public net::URLFetcherDelegate {
+ public:
+ using SnippetsAvailableCallback = base::Callback<void(const base::FilePath&)>;
+ using SnippetsAvailableCallbackList =
+ base::CallbackList<void(const base::FilePath&)>;
+
+ NTPSnippetsFetcher(scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ SigninManagerBase* signin_manager,
+ OAuth2TokenService* oauth2_token_service,
+ scoped_refptr<net::URLRequestContextGetter>
+ url_request_context_getter,
+ const base::FilePath& base_download_path);
+ ~NTPSnippetsFetcher() override;
+
+ // Fetches snippets from the server. |overwrite| is true if existing snippets
+ // should be overwritten.
+ void FetchSnippets(bool overwrite);
+
+ // Adds a callback that is called when a new set of snippets are downloaded
+ scoped_ptr<SnippetsAvailableCallbackList::Subscription> AddCallback(
+ const SnippetsAvailableCallback& callback) WARN_UNUSED_RESULT;
+
+ private:
+ void StartTokenRequest();
+ void NotifyObservers();
+ void OnDownloadSnippetsDone(bool success);
+ void OnFileExistsCheckDone(bool exists);
+ void OnFileMoveDone(bool success);
+ void StartFetch();
+
+ // OAuth2TokenService::Consumer overrides:
+ void OnGetTokenSuccess(const OAuth2TokenService::Request* request,
+ const std::string& access_token,
+ const base::Time& expiration_time) override;
+ void OnGetTokenFailure(const OAuth2TokenService::Request* request,
+ const GoogleServiceAuthError& error) override;
+
+ // OAuth2TokenService::Observer overrides:
+ void OnRefreshTokenAvailable(const std::string& account_id) override;
+
+ // URLFetcherDelegate implementation.
+ void OnURLFetchComplete(const net::URLFetcher* source) override;
+
+ // The SequencedTaskRunner on which file system operations will be run.
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
+ // Holds the URL request context.
+ scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
+
+ scoped_ptr<net::URLFetcher> url_fetcher_;
+ scoped_ptr<SigninManagerBase> signin_manager_;
+ scoped_ptr<OAuth2TokenService> token_service_;
+ scoped_ptr<OAuth2TokenService::Request> oauth_request_;
+
+ base::FilePath download_path_;
+ bool waiting_for_refresh_token_;
+
+ SnippetsAvailableCallbackList callback_list_;
+
+ base::WeakPtrFactory<NTPSnippetsFetcher> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsFetcher);
+};
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_FETCHER_H_
diff --git a/components/ntp_snippets/ntp_snippets_service.cc b/components/ntp_snippets/ntp_snippets_service.cc
index f3c0670..149ab4a 100644
--- a/components/ntp_snippets/ntp_snippets_service.cc
+++ b/components/ntp_snippets/ntp_snippets_service.cc
@@ -4,14 +4,36 @@
#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
#include "base/json/json_string_value_serializer.h"
+#include "base/location.h"
+#include "base/path_service.h"
+#include "base/task_runner_util.h"
#include "base/values.h"
namespace ntp_snippets {
+bool ReadFileToString(const base::FilePath& path, std::string* data) {
+ DCHECK(data);
+ bool success = base::ReadFileToString(path, data);
+ DLOG_IF(ERROR, !success) << "Error reading file " << path.LossyDisplayName();
+ return success;
+}
+
NTPSnippetsService::NTPSnippetsService(
- const std::string& application_language_code)
- : loaded_(false), application_language_code_(application_language_code) {}
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ const std::string& application_language_code,
+ scoped_ptr<NTPSnippetsFetcher> snippets_fetcher)
+ : loaded_(false),
+ file_task_runner_(file_task_runner),
+ application_language_code_(application_language_code),
+ snippets_fetcher_(std::move(snippets_fetcher)),
+ weak_ptr_factory_(this) {
+ snippets_fetcher_callback_ = snippets_fetcher_->AddCallback(
+ base::Bind(&NTPSnippetsService::OnSnippetsDownloaded,
+ weak_ptr_factory_.GetWeakPtr()));
+}
NTPSnippetsService::~NTPSnippetsService() {}
@@ -21,6 +43,10 @@ void NTPSnippetsService::Shutdown() {
loaded_ = false;
}
+void NTPSnippetsService::FetchSnippets(bool overwrite) {
+ snippets_fetcher_->FetchSnippets(overwrite);
+}
+
void NTPSnippetsService::AddObserver(NTPSnippetsServiceObserver* observer) {
observers_.AddObserver(observer);
if (loaded_)
@@ -31,6 +57,14 @@ void NTPSnippetsService::RemoveObserver(NTPSnippetsServiceObserver* observer) {
observers_.RemoveObserver(observer);
}
+void NTPSnippetsService::OnFileReadDone(const std::string* json, bool success) {
+ if (!success)
+ return;
+
+ DCHECK(json);
+ LoadFromJSONString(*json);
+}
+
bool NTPSnippetsService::LoadFromJSONString(const std::string& str) {
JSONStringValueDeserializer deserializer(str);
int error_code;
@@ -57,16 +91,27 @@ bool NTPSnippetsService::LoadFromJSONString(const std::string& str) {
const base::DictionaryValue* content = NULL;
if (!dict->GetDictionary("contentInfo", &content))
return false;
- std::unique_ptr<NTPSnippet> snippet =
+ scoped_ptr<NTPSnippet> snippet =
NTPSnippet::NTPSnippetFromDictionary(*content);
if (!snippet)
return false;
snippets_.push_back(std::move(snippet));
}
loaded_ = true;
+
FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_,
NTPSnippetsServiceLoaded(this));
return true;
}
+void NTPSnippetsService::OnSnippetsDownloaded(
+ const base::FilePath& download_path) {
+ std::string* downloaded_data = new std::string;
+ base::PostTaskAndReplyWithResult(
+ file_task_runner_.get(), FROM_HERE,
+ base::Bind(&ReadFileToString, download_path, downloaded_data),
+ base::Bind(&NTPSnippetsService::OnFileReadDone,
+ weak_ptr_factory_.GetWeakPtr(), base::Owned(downloaded_data)));
+}
+
} // namespace ntp_snippets
diff --git a/components/ntp_snippets/ntp_snippets_service.h b/components/ntp_snippets/ntp_snippets_service.h
index 76cb3d3..f9d2a40 100644
--- a/components/ntp_snippets/ntp_snippets_service.h
+++ b/components/ntp_snippets/ntp_snippets_service.h
@@ -11,19 +11,22 @@
#include <vector>
#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
+#include "base/sequenced_task_runner.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/ntp_snippets/inner_iterator.h"
#include "components/ntp_snippets/ntp_snippet.h"
+#include "components/ntp_snippets/ntp_snippets_fetcher.h"
namespace ntp_snippets {
class NTPSnippetsServiceObserver;
// Stores and vend fresh content data for the NTP.
-class NTPSnippetsService : public KeyedService {
+class NTPSnippetsService : public KeyedService, NTPSnippetsFetcher::Observer {
public:
- using NTPSnippetStorage = std::vector<std::unique_ptr<NTPSnippet>>;
+ using NTPSnippetStorage = std::vector<scoped_ptr<NTPSnippet>>;
using const_iterator =
InnerIterator<NTPSnippetStorage::const_iterator, const NTPSnippet>;
@@ -31,9 +34,15 @@ class NTPSnippetsService : public KeyedService {
// 'en' or 'en-US'. Note that this code should only specify the language, not
// the locale, so 'en_US' (english language with US locale) and 'en-GB_US'
// (British english person in the US) are not language code.
- explicit NTPSnippetsService(const std::string& application_language_code);
+ NTPSnippetsService(scoped_refptr<base::SequencedTaskRunner> file_task_runner,
+ const std::string& application_language_code,
+ scoped_ptr<NTPSnippetsFetcher> snippets_fetcher);
~NTPSnippetsService() override;
+ // Fetches snippets from the server. |overwrite| is true if existing snippets
+ // should be overwritten.
+ void FetchSnippets(bool overwrite);
+
// Inherited from KeyedService.
void Shutdown() override;
@@ -44,11 +53,6 @@ class NTPSnippetsService : public KeyedService {
void AddObserver(NTPSnippetsServiceObserver* observer);
void RemoveObserver(NTPSnippetsServiceObserver* observer);
- // Expects the JSON to be a list of dictionaries with keys matching the
- // properties of a snippet (url, title, site_title, etc...). The url is the
- // only mandatory value.
- bool LoadFromJSONString(const std::string& str);
-
// Number of snippets available. Can only be called when is_loaded() is true.
NTPSnippetStorage::size_type size() {
DCHECK(loaded_);
@@ -72,15 +76,44 @@ class NTPSnippetsService : public KeyedService {
}
private:
+ void OnFileReadDone(const std::string* json, bool success);
+ void OnSnippetsDownloaded(const base::FilePath& download_path);
+
+ // Expects the JSON to be a list of dictionaries with keys matching the
+ // properties of a snippet (url, title, site_title, etc...). The url is the
+ // only mandatory value.
+ bool LoadFromJSONString(const std::string& str);
+
// True if the suggestions are loaded.
bool loaded_;
+
+ // The SequencedTaskRunner on which file system operations will be run.
+ scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
+
// All the suggestions.
NTPSnippetStorage snippets_;
+
// The ISO 639-1 code of the language used by the application.
const std::string application_language_code_;
+
// The observers.
base::ObserverList<NTPSnippetsServiceObserver> observers_;
+ // The snippets fetcher
+ scoped_ptr<NTPSnippetsFetcher> snippets_fetcher_;
+
+ // The callback from the snippets fetcher
+ scoped_ptr<NTPSnippetsFetcher::SnippetsAvailableCallbackList::Subscription>
+ snippets_fetcher_callback_;
+
+ base::WeakPtrFactory<NTPSnippetsService> weak_ptr_factory_;
+
+ friend class NTPSnippetsServiceTest;
+ FRIEND_TEST_ALL_PREFIXES(NTPSnippetsServiceTest, Loop);
+ FRIEND_TEST_ALL_PREFIXES(NTPSnippetsServiceTest, Full);
+ FRIEND_TEST_ALL_PREFIXES(NTPSnippetsServiceTest, ObserverLoaded);
+ FRIEND_TEST_ALL_PREFIXES(NTPSnippetsServiceTest, ObserverNotLoaded);
+
DISALLOW_COPY_AND_ASSIGN(NTPSnippetsService);
};
diff --git a/components/ntp_snippets/ntp_snippets_service_unittest.cc b/components/ntp_snippets/ntp_snippets_service_unittest.cc
index df1cf7b..f801e94 100644
--- a/components/ntp_snippets/ntp_snippets_service_unittest.cc
+++ b/components/ntp_snippets/ntp_snippets_service_unittest.cc
@@ -3,25 +3,31 @@
// found in the LICENSE file.
#include "base/macros.h"
+#include "base/message_loop/message_loop.h"
+#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/ntp_snippets/ntp_snippet.h"
+#include "components/ntp_snippets/ntp_snippets_fetcher.h"
#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "components/signin/core/browser/account_tracker_service.h"
+#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
+#include "components/signin/core/browser/fake_signin_manager.h"
+#include "components/signin/core/browser/test_signin_client.h"
+#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
-namespace {
+namespace ntp_snippets {
-class SnippetObserver : public ntp_snippets::NTPSnippetsServiceObserver {
+class SnippetObserver : public NTPSnippetsServiceObserver {
public:
SnippetObserver() : loaded_(false), shutdown_(false) {}
~SnippetObserver() override {}
- void NTPSnippetsServiceLoaded(
- ntp_snippets::NTPSnippetsService* service) override {
+ void NTPSnippetsServiceLoaded(NTPSnippetsService* service) override {
loaded_ = true;
}
- void NTPSnippetsServiceShutdown(
- ntp_snippets::NTPSnippetsService* service) override {
+ void NTPSnippetsServiceShutdown(NTPSnippetsService* service) override {
shutdown_ = true;
loaded_ = false;
}
@@ -38,22 +44,45 @@ class NTPSnippetsServiceTest : public testing::Test {
NTPSnippetsServiceTest() {}
~NTPSnippetsServiceTest() override {}
+ void SetUp() override {
+ signin_client_.reset(new TestSigninClient(nullptr));
+ account_tracker_.reset(new AccountTrackerService());
+ }
+
+ protected:
+ scoped_ptr<NTPSnippetsService> CreateSnippetService() {
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner(
+ base::ThreadTaskRunnerHandle::Get());
+ scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
+ new net::TestURLRequestContextGetter(task_runner.get());
+ FakeProfileOAuth2TokenService* token_service =
+ new FakeProfileOAuth2TokenService();
+ FakeSigninManagerBase* signin_manager = new FakeSigninManagerBase(
+ signin_client_.get(), account_tracker_.get());
+
+ scoped_ptr<NTPSnippetsService> service(
+ new NTPSnippetsService(task_runner.get(), std::string("fr"),
+ make_scoped_ptr(new NTPSnippetsFetcher(task_runner.get(),
+ signin_manager, token_service, request_context_getter,
+ base::FilePath()))));
+ return service;
+ }
+
private:
+ scoped_ptr<AccountTrackerService> account_tracker_;
+ scoped_ptr<TestSigninClient> signin_client_;
+ base::MessageLoop message_loop_;
DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest);
};
-TEST_F(NTPSnippetsServiceTest, Create) {
- std::string language_code("fr");
- scoped_ptr<ntp_snippets::NTPSnippetsService> service(
- new ntp_snippets::NTPSnippetsService(language_code));
+TEST_F(NTPSnippetsServiceTest, Create) {
+ scoped_ptr<NTPSnippetsService> service(CreateSnippetService());
EXPECT_FALSE(service->is_loaded());
}
TEST_F(NTPSnippetsServiceTest, Loop) {
- std::string language_code("fr");
- scoped_ptr<ntp_snippets::NTPSnippetsService> service(
- new ntp_snippets::NTPSnippetsService(language_code));
+ scoped_ptr<NTPSnippetsService> service(CreateSnippetService());
EXPECT_FALSE(service->is_loaded());
@@ -71,15 +100,13 @@ TEST_F(NTPSnippetsServiceTest, Loop) {
EXPECT_EQ(snippet.url(), GURL("http://localhost/foobar"));
}
// Without the const, this should not compile.
- for (const ntp_snippets::NTPSnippet& snippet : *service) {
+ for (const NTPSnippet& snippet : *service) {
EXPECT_EQ(snippet.url(), GURL("http://localhost/foobar"));
}
}
TEST_F(NTPSnippetsServiceTest, Full) {
- std::string language_code("fr");
- scoped_ptr<ntp_snippets::NTPSnippetsService> service(
- new ntp_snippets::NTPSnippetsService(language_code));
+ scoped_ptr<NTPSnippetsService> service(CreateSnippetService());
std::string json_str(
"{ \"recos\": [ "
@@ -112,9 +139,7 @@ TEST_F(NTPSnippetsServiceTest, Full) {
}
TEST_F(NTPSnippetsServiceTest, ObserverNotLoaded) {
- std::string language_code("fr");
- scoped_ptr<ntp_snippets::NTPSnippetsService> service(
- new ntp_snippets::NTPSnippetsService(language_code));
+ scoped_ptr<NTPSnippetsService> service(CreateSnippetService());
SnippetObserver observer;
service->AddObserver(&observer);
@@ -131,9 +156,7 @@ TEST_F(NTPSnippetsServiceTest, ObserverNotLoaded) {
}
TEST_F(NTPSnippetsServiceTest, ObserverLoaded) {
- std::string language_code("fr");
- scoped_ptr<ntp_snippets::NTPSnippetsService> service(
- new ntp_snippets::NTPSnippetsService(language_code));
+ scoped_ptr<NTPSnippetsService> service(CreateSnippetService());
std::string json_str(
"{ \"recos\": [ "
@@ -148,4 +171,4 @@ TEST_F(NTPSnippetsServiceTest, ObserverLoaded) {
service->RemoveObserver(&observer);
}
-} // namespace
+} // namespace ntp_snippets
diff --git a/ios/chrome/browser/ntp_snippets/ios_chrome_ntp_snippets_service_factory.cc b/ios/chrome/browser/ntp_snippets/ios_chrome_ntp_snippets_service_factory.cc
index 4fa922e..0602d85 100644
--- a/ios/chrome/browser/ntp_snippets/ios_chrome_ntp_snippets_service_factory.cc
+++ b/ios/chrome/browser/ntp_snippets/ios_chrome_ntp_snippets_service_factory.cc
@@ -7,9 +7,17 @@
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "components/keyed_service/ios/browser_state_dependency_manager.h"
+#include "components/ntp_snippets/ntp_snippets_fetcher.h"
#include "components/ntp_snippets/ntp_snippets_service.h"
+#include "components/signin/core/browser/profile_oauth2_token_service.h"
+#include "google_apis/gaia/oauth2_token_service.h"
#include "ios/chrome/browser/application_context.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/signin/oauth2_token_service_factory.h"
+#include "ios/chrome/browser/signin/signin_manager_factory.h"
+#include "ios/web/public/browser_state.h"
+#include "ios/web/public/web_thread.h"
+#include "net/url_request/url_request_context_getter.h"
// static
IOSChromeNTPSnippetsServiceFactory*
@@ -36,7 +44,24 @@ IOSChromeNTPSnippetsServiceFactory::~IOSChromeNTPSnippetsServiceFactory() {}
scoped_ptr<KeyedService>
IOSChromeNTPSnippetsServiceFactory::BuildServiceInstanceFor(
web::BrowserState* browser_state) const {
+ ios::ChromeBrowserState* chrome_browser_state =
+ ios::ChromeBrowserState::FromBrowserState(browser_state);
DCHECK(!browser_state->IsOffTheRecord());
+ SigninManager* signin_manager =
+ ios::SigninManagerFactory::GetForBrowserState(chrome_browser_state);
+ OAuth2TokenService* token_service =
+ OAuth2TokenServiceFactory::GetForBrowserState(chrome_browser_state);
+ scoped_refptr<net::URLRequestContextGetter> request_context =
+ browser_state->GetRequestContext();
+
+ scoped_refptr<base::SequencedTaskRunner> task_runner =
+ web::WebThread::GetBlockingPool()
+ ->GetSequencedTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::GetSequenceToken(),
+ base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
return make_scoped_ptr(new ntp_snippets::NTPSnippetsService(
- GetApplicationContext()->GetApplicationLocale()));
+ task_runner, GetApplicationContext()->GetApplicationLocale(),
+ make_scoped_ptr(new ntp_snippets::NTPSnippetsFetcher(
+ task_runner, (SigninManagerBase*)signin_manager, token_service,
+ request_context, browser_state->GetStatePath()))));
}