summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortreib <treib@chromium.org>2016-02-25 07:44:40 -0800
committerCommit bot <commit-bot@chromium.org>2016-02-25 15:45:53 +0000
commite6aa5c3359e84aea9a4be25dff250a4ca3fef35e (patch)
treeba64846463f698da47538affc1e019b8f6bca234
parent438cbb9ca139d96fb82211cb87fef4c072e29b7b (diff)
downloadchromium_src-e6aa5c3359e84aea9a4be25dff250a4ca3fef35e.zip
chromium_src-e6aa5c3359e84aea9a4be25dff250a4ca3fef35e.tar.gz
chromium_src-e6aa5c3359e84aea9a4be25dff250a4ca3fef35e.tar.bz2
[NTP Snippets] Schedule periodic fetching
BUG=586452 Review URL: https://codereview.chromium.org/1699143002 Cr-Commit-Position: refs/heads/master@{#377581}
-rw-r--r--chrome/android/java/AndroidManifest.xml4
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java8
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncherService.java69
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java7
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java101
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsController.java11
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsLauncher.java132
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherServiceTest.java76
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java125
-rw-r--r--chrome/browser/android/chrome_jni_registrar.cc2
-rw-r--r--chrome/browser/android/ntp_snippets_launcher.cc60
-rw-r--r--chrome/browser/android/ntp_snippets_launcher.h38
-rw-r--r--chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc12
-rw-r--r--chrome/browser/profiles/profile_manager.cc15
-rw-r--r--chrome/chrome_browser.gypi3
-rw-r--r--components/ntp_snippets.gypi1
-rw-r--r--components/ntp_snippets/ntp_snippets_scheduler.h32
-rw-r--r--components/ntp_snippets/ntp_snippets_service.cc23
-rw-r--r--components/ntp_snippets/ntp_snippets_service.h7
-rw-r--r--components/ntp_snippets/ntp_snippets_service_unittest.cc3
-rw-r--r--ios/chrome/browser/ntp_snippets/ios_chrome_ntp_snippets_service_factory.cc4
21 files changed, 568 insertions, 165 deletions
diff --git a/chrome/android/java/AndroidManifest.xml b/chrome/android/java/AndroidManifest.xml
index 0ca1758..369ba35 100644
--- a/chrome/android/java/AndroidManifest.xml
+++ b/chrome/android/java/AndroidManifest.xml
@@ -579,8 +579,8 @@ by a child template that "extends" this file.
</intent-filter>
</receiver>
- <!-- Service Worker Background Sync GCM scheduler task -->
- <service android:name="org.chromium.chrome.browser.BackgroundSyncLauncherService"
+ <!-- GcmTaskService implementation to wake Chrome on scheduled events -->
+ <service android:name="org.chromium.chrome.browser.ChromeBackgroundService"
android:permission="com.google.android.gms.permission.BIND_NETWORK_TASK_SERVICE"
android:exported="true">
<intent-filter>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java
index ffec5a9..69537e4 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java
@@ -30,6 +30,8 @@ import org.chromium.chrome.browser.externalauth.UserRecoverableErrorHandler;
public class BackgroundSyncLauncher {
private static final String TAG = "BgSyncLauncher";
+ public static final String TASK_TAG = "BackgroundSync Event";
+
static final String PREF_BACKGROUND_SYNC_LAUNCH_NEXT_ONLINE = "bgsync_launch_next_online";
// The instance of BackgroundSyncLauncher currently owned by a C++
// BackgroundSyncLauncherAndroid, if any. If it is non-null then the browser is running.
@@ -191,8 +193,8 @@ public class BackgroundSyncLauncher {
// the Play Store. In this case, scheduling the task will fail silently.
final long minDelaySecs = minDelayMs / 1000;
OneoffTask oneoff = new OneoffTask.Builder()
- .setService(BackgroundSyncLauncherService.class)
- .setTag("BackgroundSync Event")
+ .setService(ChromeBackgroundService.class)
+ .setTag(TASK_TAG)
// We have to set a non-zero execution window here
.setExecutionWindow(minDelaySecs, minDelaySecs + 1)
.setRequiredNetwork(Task.NETWORK_STATE_CONNECTED)
@@ -212,7 +214,7 @@ public class BackgroundSyncLauncher {
private static boolean removeScheduledTasks(GcmNetworkManager scheduler) {
try {
- scheduler.cancelAllTasks(BackgroundSyncLauncherService.class);
+ scheduler.cancelTask(TASK_TAG, ChromeBackgroundService.class);
} catch (IllegalArgumentException e) {
// This occurs when BackgroundSyncLauncherService is not found in the application
// manifest. This should not happen in code that reaches here, but has been seen in
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncherService.java b/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncherService.java
deleted file mode 100644
index a6219fd..0000000
--- a/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncherService.java
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright 2015 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;
-
-import android.content.Context;
-
-import com.google.android.gms.gcm.GcmNetworkManager;
-import com.google.android.gms.gcm.GcmTaskService;
-import com.google.android.gms.gcm.TaskParams;
-
-import org.chromium.base.Log;
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.VisibleForTesting;
-import org.chromium.base.annotations.SuppressFBWarnings;
-import org.chromium.base.library_loader.LibraryProcessType;
-import org.chromium.base.library_loader.ProcessInitException;
-import org.chromium.content.app.ContentApplication;
-import org.chromium.content.browser.BrowserStartupController;
-
-/**
- * {@link BackgroundSyncLauncherService} is scheduled through the {@link GcmNetworkManager}
- * when the browser needs to be launched in response to changing network or power conditions.
- */
-public class BackgroundSyncLauncherService extends GcmTaskService {
- private static final String TAG = "BgSyncLauncher";
-
- @Override
- @VisibleForTesting
- public int onRunTask(TaskParams params) {
- // Start the browser. The browser's BackgroundSyncManager (for the active profile) will
- // start, check the network, and run any necessary sync events. This task runs with a wake
- // lock, but has a three minute timeout, so we need to start the browser in its own task.
- // TODO(jkarlin): Protect the browser sync event with a wake lock. See crbug.com/486020.
- Log.v(TAG, "Starting Browser after coming online");
- final Context context = this;
- ThreadUtils.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (!BackgroundSyncLauncher.hasInstance()) {
- launchBrowser(context);
- }
- }
- });
- return GcmNetworkManager.RESULT_SUCCESS;
- }
-
- @VisibleForTesting
- @SuppressFBWarnings("DM_EXIT")
- protected void launchBrowser(Context context) {
- ContentApplication.initCommandLine(context);
- try {
- BrowserStartupController.get(context, LibraryProcessType.PROCESS_BROWSER)
- .startBrowserProcessesSync(false);
- } catch (ProcessInitException e) {
- Log.e(TAG, "ProcessInitException while starting the browser process");
- // Since the library failed to initialize nothing in the application
- // can work, so kill the whole application not just the activity.
- System.exit(-1);
- }
- }
-
- @Override
- @VisibleForTesting
- public void onInitializeTasks() {
- BackgroundSyncLauncher.rescheduleTasksOnUpgrade(this);
- }
-}
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 7fe5a78..fb7f9206 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeActivity.java
@@ -89,7 +89,6 @@ 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;
@@ -694,12 +693,6 @@ 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 (ChromeFeatureList.isEnabled(ChromeFeatureList.NTP_SNIPPETS)) {
- // Initialize snippets
- SnippetsController.get(this).fetchSnippets(false);
- }
}
@Override
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
new file mode 100644
index 0000000..7828402
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
@@ -0,0 +1,101 @@
+// 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;
+
+import android.content.Context;
+
+import com.google.android.gms.gcm.GcmNetworkManager;
+import com.google.android.gms.gcm.GcmTaskService;
+import com.google.android.gms.gcm.TaskParams;
+
+import org.chromium.base.Log;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.base.library_loader.LibraryProcessType;
+import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsController;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsLauncher;
+import org.chromium.content.app.ContentApplication;
+import org.chromium.content.browser.BrowserStartupController;
+
+/**
+ * {@link ChromeBackgroundService} is scheduled through the {@link GcmNetworkManager} when the
+ * browser needs to be launched for scheduled tasks, or in response to changing network or power
+ * conditions.
+ */
+public class ChromeBackgroundService extends GcmTaskService {
+ private static final String TAG = "BackgroundService";
+
+ @Override
+ public int onRunTask(TaskParams params) {
+ Log.i(TAG, "Woken up at " + new java.util.Date().toString());
+ handleRunTask(params.getTag());
+ return GcmNetworkManager.RESULT_SUCCESS;
+ }
+
+ @VisibleForTesting
+ public void handleRunTask(final String tag) {
+ final Context context = this;
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ switch(tag) {
+ case BackgroundSyncLauncher.TASK_TAG:
+ handleBackgroundSyncEvent(context);
+ break;
+
+ case SnippetsLauncher.TASK_TAG:
+ handleFetchSnippets(context);
+ break;
+
+ default:
+ Log.i(TAG, "Unknown task tag " + tag);
+ break;
+ }
+ }
+ });
+ }
+
+ private void handleBackgroundSyncEvent(Context context) {
+ if (!BackgroundSyncLauncher.hasInstance()) {
+ // Start the browser. The browser's BackgroundSyncManager (for the active profile) will
+ // start, check the network, and run any necessary sync events. This task runs with a
+ // wake lock, but has a three minute timeout, so we need to start the browser in its
+ // own task.
+ // TODO(jkarlin): Protect the browser sync event with a wake lock.
+ // See crbug.com/486020.
+ launchBrowser(context);
+ }
+ }
+
+ private void handleFetchSnippets(Context context) {
+ if (!SnippetsLauncher.hasInstance()) {
+ launchBrowser(context);
+ }
+ SnippetsController.get(context).fetchSnippets(true);
+ }
+
+ @VisibleForTesting
+ @SuppressFBWarnings("DM_EXIT")
+ protected void launchBrowser(Context context) {
+ Log.i(TAG, "Launching browser");
+ ContentApplication.initCommandLine(context);
+ try {
+ BrowserStartupController.get(context, LibraryProcessType.PROCESS_BROWSER)
+ .startBrowserProcessesSync(false);
+ } catch (ProcessInitException e) {
+ Log.e(TAG, "ProcessInitException while starting the browser process");
+ // Since the library failed to initialize nothing in the application
+ // can work, so kill the whole application not just the activity.
+ System.exit(-1);
+ }
+ }
+
+ @Override
+ public void onInitializeTasks() {
+ BackgroundSyncLauncher.rescheduleTasksOnUpgrade(this);
+ }
+}
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
index 55dd84c..e11d187 100644
--- 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
@@ -7,6 +7,7 @@ package org.chromium.chrome.browser.ntp.snippets;
import android.content.Context;
import org.chromium.base.ThreadUtils;
+import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.SigninManager;
import org.chromium.chrome.browser.signin.SigninManager.SignInStateObserver;
@@ -20,7 +21,10 @@ public class SnippetsController implements SignInStateObserver {
private long mNativeSnippetsController;
public SnippetsController(Context applicationContext) {
- SigninManager.get(applicationContext).addSignInStateObserver(this);
+ // |applicationContext| can be null in tests.
+ if (applicationContext != null) {
+ SigninManager.get(applicationContext).addSignInStateObserver(this);
+ }
}
/**
@@ -55,5 +59,10 @@ public class SnippetsController implements SignInStateObserver {
@Override
public void onSignedOut() {}
+ @VisibleForTesting
+ public static void setInstanceForTesting(SnippetsController instance) {
+ sInstance = instance;
+ }
+
private native void nativeFetchSnippets(Profile profile, boolean overwrite);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsLauncher.java b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsLauncher.java
new file mode 100644
index 0000000..18ad921
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ntp/snippets/SnippetsLauncher.java
@@ -0,0 +1,132 @@
+// 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 com.google.android.gms.gcm.GcmNetworkManager;
+import com.google.android.gms.gcm.PeriodicTask;
+import com.google.android.gms.gcm.Task;
+
+import org.chromium.base.Log;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.CalledByNative;
+import org.chromium.base.annotations.SuppressFBWarnings;
+import org.chromium.chrome.browser.ChromeBackgroundService;
+import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
+import org.chromium.chrome.browser.externalauth.UserRecoverableErrorHandler;
+
+/**
+ * The {@link SnippetsLauncher} singleton is created and owned by the C++ browser.
+ *
+ * Thread model: This class is to be run on the UI thread only.
+ */
+public class SnippetsLauncher {
+ private static final String TAG = "SnippetsLauncher";
+
+ public static final String TASK_TAG = "FetchSnippets";
+
+ // The instance of SnippetsLauncher currently owned by a C++ SnippetsLauncherAndroid, if any.
+ // If it is non-null then the browser is running.
+ private static SnippetsLauncher sInstance;
+
+ private GcmNetworkManager mScheduler;
+
+ private boolean mGCMEnabled = true;
+
+ /**
+ * Create a SnippetsLauncher object, which is owned by C++.
+ * @param context The app context.
+ */
+ @VisibleForTesting
+ @CalledByNative
+ public static SnippetsLauncher create(Context context) {
+ if (sInstance != null) {
+ throw new IllegalStateException("Already instantiated");
+ }
+
+ sInstance = new SnippetsLauncher(context);
+ return sInstance;
+ }
+
+ /**
+ * Called when the C++ counterpart is deleted.
+ */
+ @VisibleForTesting
+ @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
+ @CalledByNative
+ public void destroy() {
+ assert sInstance == this;
+ sInstance = null;
+ }
+
+ /**
+ * Returns true if the native browser has started and created an instance of {@link
+ * SnippetsLauncher}.
+ */
+ public static boolean hasInstance() {
+ return sInstance != null;
+ }
+
+ protected SnippetsLauncher(Context context) {
+ checkGCM(context);
+ mScheduler = GcmNetworkManager.getInstance(context);
+ }
+
+ private boolean canUseGooglePlayServices(Context context) {
+ return ExternalAuthUtils.getInstance().canUseGooglePlayServices(
+ context, new UserRecoverableErrorHandler.Silent());
+ }
+
+ private void checkGCM(Context context) {
+ // Check to see if Play Services is up to date, and disable GCM if not.
+ if (!canUseGooglePlayServices(context)) {
+ mGCMEnabled = false;
+ Log.i(TAG, "Disabling SnippetsLauncher because Play Services is not up to date.");
+ }
+ }
+
+ @CalledByNative
+ private boolean schedule(int periodSeconds) {
+ if (!mGCMEnabled) return false;
+ Log.i(TAG, "Scheduling: " + periodSeconds);
+ // Google Play Services may not be up to date, if the application was not installed through
+ // the Play Store. In this case, scheduling the task will fail silently.
+ PeriodicTask task = new PeriodicTask.Builder()
+ .setService(ChromeBackgroundService.class)
+ .setTag(TASK_TAG)
+ .setPeriod(periodSeconds)
+ .setRequiredNetwork(Task.NETWORK_STATE_UNMETERED)
+ .setPersisted(true)
+ .setUpdateCurrent(true)
+ .build();
+ try {
+ mScheduler.schedule(task);
+ } catch (IllegalArgumentException e) {
+ // Disable GCM for the remainder of this session.
+ mGCMEnabled = false;
+ // Return false so that the failure will be logged.
+ return false;
+ }
+ return true;
+ }
+
+ @CalledByNative
+ private boolean unschedule() {
+ if (!mGCMEnabled) return false;
+ Log.i(TAG, "Unscheduling");
+ try {
+ mScheduler.cancelTask("SnippetsLauncher", ChromeBackgroundService.class);
+ } catch (IllegalArgumentException e) {
+ // This occurs when SnippetsLauncherService is not found in the application
+ // manifest. Disable GCM for the remainder of this session.
+ mGCMEnabled = false;
+ // Return false so that the failure will be logged.
+ return false;
+ }
+ return true;
+ }
+}
+
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherServiceTest.java
deleted file mode 100644
index e626c25..0000000
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherServiceTest.java
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2015 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;
-
-import android.content.Context;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import org.chromium.base.ThreadUtils;
-import org.chromium.base.metrics.RecordHistogram;
-import org.chromium.base.test.util.AdvancedMockContext;
-import org.chromium.base.test.util.Feature;
-
-/**
- * Tests {@link BackgroundSyncLauncherService}.
- */
-public class BackgroundSyncLauncherServiceTest extends InstrumentationTestCase {
- private Context mContext;
- private BackgroundSyncLauncher mLauncher;
- private MockLauncherService mLauncherService;
-
- static class MockLauncherService extends BackgroundSyncLauncherService {
- private boolean mDidStartService = false;
-
- @Override
- protected void launchBrowser(Context context) {
- mDidStartService = true;
- }
-
- // Posts an assertion task to the UI thread. Since this is only called after the call
- // to onRunTask, it will be enqueued after any possible call to launchBrowser, and we
- // can reliably check whether launchBrowser was called.
- protected void checkExpectations(final boolean expectedStartService) {
- ThreadUtils.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- assertEquals("StartedService", expectedStartService, mDidStartService);
- }
- });
- }
- }
-
- @Override
- protected void setUp() throws Exception {
- mContext = new AdvancedMockContext(getInstrumentation().getTargetContext());
- BackgroundSyncLauncher.setGCMEnabled(false);
- RecordHistogram.disableForTests();
- mLauncher = BackgroundSyncLauncher.create(mContext);
- mLauncherService = new MockLauncherService();
- }
-
- private void deleteLauncherInstance() {
- mLauncher.destroy();
- mLauncher = null;
- }
-
- private void startOnRunTaskAndVerify(boolean shouldStart) {
- mLauncherService.onRunTask(null);
- mLauncherService.checkExpectations(shouldStart);
- }
-
- @SmallTest
- @Feature({"BackgroundSync"})
- public void testNoFireWhenInstanceExists() {
- startOnRunTaskAndVerify(false);
- }
-
- @SmallTest
- @Feature({"BackgroundSync"})
- public void testFiresWhenInstanceDoesNotExist() {
- deleteLauncherInstance();
- startOnRunTaskAndVerify(true);
- }
-}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
new file mode 100644
index 0000000..fb25f1f
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
@@ -0,0 +1,125 @@
+// 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;
+
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.base.test.util.AdvancedMockContext;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsController;
+import org.chromium.chrome.browser.ntp.snippets.SnippetsLauncher;
+
+/**
+ * Tests {@link ChromeBackgroundService}.
+ */
+public class ChromeBackgroundServiceTest extends InstrumentationTestCase {
+ private Context mContext;
+ private BackgroundSyncLauncher mSyncLauncher;
+ private SnippetsLauncher mSnippetsLauncher;
+ private MockSnippetsController mSnippetsController;
+ private MockTaskService mTaskService;
+
+ static class MockTaskService extends ChromeBackgroundService {
+ private boolean mDidLaunchBrowser = false;
+
+ @Override
+ protected void launchBrowser(Context context) {
+ mDidLaunchBrowser = true;
+ }
+
+ // Posts an assertion task to the UI thread. Since this is only called after the call
+ // to onRunTask, it will be enqueued after any possible call to launchBrowser, and we
+ // can reliably check whether launchBrowser was called.
+ protected void checkExpectations(final boolean expectedLaunchBrowser) {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("StartedService", expectedLaunchBrowser, mDidLaunchBrowser);
+ }
+ });
+ }
+ }
+
+ static class MockSnippetsController extends SnippetsController {
+ private boolean mDidFetchSnippets = false;
+
+ MockSnippetsController() {
+ super(null);
+ }
+
+ @Override
+ public void fetchSnippets(boolean overwrite) {
+ mDidFetchSnippets = true;
+ }
+
+ protected void checkExpectations(final boolean expectedFetchSnippets) {
+ ThreadUtils.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertEquals("FetchedSnippets", expectedFetchSnippets, mDidFetchSnippets);
+ }
+ });
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mContext = new AdvancedMockContext(getInstrumentation().getTargetContext());
+ BackgroundSyncLauncher.setGCMEnabled(false);
+ RecordHistogram.disableForTests();
+ mSyncLauncher = BackgroundSyncLauncher.create(mContext);
+ mSnippetsLauncher = SnippetsLauncher.create(mContext);
+ mSnippetsController = new MockSnippetsController();
+ SnippetsController.setInstanceForTesting(mSnippetsController);
+ mTaskService = new MockTaskService();
+ }
+
+ private void deleteSyncLauncherInstance() {
+ mSyncLauncher.destroy();
+ mSyncLauncher = null;
+ }
+
+ private void deleteSnippetsLauncherInstance() {
+ mSnippetsLauncher.destroy();
+ mSnippetsLauncher = null;
+ }
+
+ private void startOnRunTaskAndVerify(String taskTag, boolean shouldStart,
+ boolean shouldFetchSnippets) {
+ mTaskService.handleRunTask(taskTag);
+ mTaskService.checkExpectations(shouldStart);
+ mSnippetsController.checkExpectations(shouldFetchSnippets);
+ }
+
+ @SmallTest
+ @Feature({"BackgroundSync"})
+ public void testBackgroundSyncNoLaunchBrowserWhenInstanceExists() {
+ startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, false, false);
+ }
+
+ @SmallTest
+ @Feature({"BackgroundSync"})
+ public void testBackgroundSyncLaunchBrowserWhenInstanceDoesNotExist() {
+ deleteSyncLauncherInstance();
+ startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, true, false);
+ }
+
+ @SmallTest
+ @Feature({"NTPSnippets"})
+ public void testNTPSnippetsNoLaunchBrowserWhenInstanceExists() {
+ startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG, false, true);
+ }
+
+ @SmallTest
+ @Feature({"NTPSnippets"})
+ public void testNTPSnippetsLaunchBrowserWhenInstanceDoesNotExist() {
+ deleteSnippetsLauncherInstance();
+ startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG, true, true);
+ }
+}
diff --git a/chrome/browser/android/chrome_jni_registrar.cc b/chrome/browser/android/chrome_jni_registrar.cc
index 8aca352..ddf0b94 100644
--- a/chrome/browser/android/chrome_jni_registrar.cc
+++ b/chrome/browser/android/chrome_jni_registrar.cc
@@ -62,6 +62,7 @@
#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/ntp_snippets_launcher.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"
@@ -310,6 +311,7 @@ static base::android::RegistrationMethod kChromeRegisteredMethods[] = {
NotificationUIManagerAndroid::RegisterNotificationUIManager},
{"NTPSnippetsBridge", NTPSnippetsBridge::Register},
{"NTPSnippetsController", NTPSnippetsController::Register},
+ {"NTPSnippetsLauncher", NTPSnippetsLauncher::Register},
{"OAuth2TokenServiceDelegateAndroid",
OAuth2TokenServiceDelegateAndroid::Register},
{"OfflinePageBridge", offline_pages::android::RegisterOfflinePageBridge},
diff --git a/chrome/browser/android/ntp_snippets_launcher.cc b/chrome/browser/android/ntp_snippets_launcher.cc
new file mode 100644
index 0000000..3b05465
--- /dev/null
+++ b/chrome/browser/android/ntp_snippets_launcher.cc
@@ -0,0 +1,60 @@
+// 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_launcher.h"
+
+#include "base/android/context_utils.h"
+#include "content/public/browser/browser_thread.h"
+#include "jni/SnippetsLauncher_jni.h"
+
+using content::BrowserThread;
+
+namespace {
+
+base::LazyInstance<NTPSnippetsLauncher> g_snippets_launcher =
+ LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static
+NTPSnippetsLauncher* NTPSnippetsLauncher::Get() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return g_snippets_launcher.Pointer();
+}
+
+// static
+bool NTPSnippetsLauncher::Register(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+bool NTPSnippetsLauncher::Schedule(int period_seconds) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_SnippetsLauncher_schedule(
+ env, java_launcher_.obj(), period_seconds);
+}
+
+bool NTPSnippetsLauncher::Unschedule() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ return Java_SnippetsLauncher_unschedule(env, java_launcher_.obj());
+}
+
+NTPSnippetsLauncher::NTPSnippetsLauncher() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ java_launcher_.Reset(Java_SnippetsLauncher_create(
+ env, base::android::GetApplicationContext()));
+}
+
+NTPSnippetsLauncher::~NTPSnippetsLauncher() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_SnippetsLauncher_destroy(env, java_launcher_.obj());
+ java_launcher_.Reset();
+}
diff --git a/chrome/browser/android/ntp_snippets_launcher.h b/chrome/browser/android/ntp_snippets_launcher.h
new file mode 100644
index 0000000..c2f4ef6
--- /dev/null
+++ b/chrome/browser/android/ntp_snippets_launcher.h
@@ -0,0 +1,38 @@
+// 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_LAUNCHER_H_
+#define CHROME_BROWSER_ANDROID_NTP_SNIPPETS_LAUNCHER_H_
+
+#include "base/android/jni_android.h"
+#include "base/lazy_instance.h"
+#include "base/macros.h"
+#include "components/ntp_snippets/ntp_snippets_scheduler.h"
+
+// Android implementation of ntp_snippets::NTPSnippetsScheduler.
+// The NTPSnippetsLauncher singleton owns the Java SnippetsLauncher object, and
+// is used to schedule the fetching of snippets. Runs on the UI thread.
+class NTPSnippetsLauncher : public ntp_snippets::NTPSnippetsScheduler {
+ public:
+ static NTPSnippetsLauncher* Get();
+
+ static bool Register(JNIEnv* env);
+
+ // ntp_snippets::NTPSnippetsScheduler implementation.
+ bool Schedule(int period_seconds) override;
+ bool Unschedule() override;
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<NTPSnippetsLauncher>;
+
+ // Constructor and destructor marked private to enforce singleton.
+ NTPSnippetsLauncher();
+ virtual ~NTPSnippetsLauncher();
+
+ base::android::ScopedJavaGlobalRef<jobject> java_launcher_;
+
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsLauncher);
+};
+
+#endif // CHROME_BROWSER_ANDROID_NTP_SNIPPETS_LAUNCHER_H_
diff --git a/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc b/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc
index 0cf2dba..50a2294 100644
--- a/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc
+++ b/chrome/browser/ntp_snippets/ntp_snippets_service_factory.cc
@@ -12,6 +12,7 @@
#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_scheduler.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"
@@ -19,6 +20,10 @@
#include "content/public/browser/browser_thread.h"
#include "net/url_request/url_request_context_getter.h"
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/ntp_snippets_launcher.h"
+#endif // OS_ANDROID
+
using content::BrowserThread;
// static
@@ -51,6 +56,11 @@ KeyedService* NTPSnippetsServiceFactory::BuildServiceInstanceFor(
scoped_refptr<net::URLRequestContextGetter> request_context =
context->GetRequestContext();
+ ntp_snippets::NTPSnippetsScheduler* scheduler = nullptr;
+#if defined(OS_ANDROID)
+ scheduler = NTPSnippetsLauncher::Get();
+#endif // OS_ANDROID
+
scoped_refptr<base::SequencedTaskRunner> task_runner =
BrowserThread::GetBlockingPool()
->GetSequencedTaskRunnerWithShutdownBehavior(
@@ -58,7 +68,7 @@ KeyedService* NTPSnippetsServiceFactory::BuildServiceInstanceFor(
base::SequencedWorkerPool::CONTINUE_ON_SHUTDOWN);
return new ntp_snippets::NTPSnippetsService(
- task_runner, g_browser_process->GetApplicationLocale(),
+ task_runner, g_browser_process->GetApplicationLocale(), scheduler,
make_scoped_ptr(new ntp_snippets::NTPSnippetsFetcher(
task_runner, signin_manager, token_service, request_context,
profile->GetPath())));
diff --git a/chrome/browser/profiles/profile_manager.cc b/chrome/browser/profiles/profile_manager.cc
index 11d852b..a5de323 100644
--- a/chrome/browser/profiles/profile_manager.cc
+++ b/chrome/browser/profiles/profile_manager.cc
@@ -12,6 +12,7 @@
#include "base/bind.h"
#include "base/command_line.h"
#include "base/deferred_sequenced_task_runner.h"
+#include "base/feature_list.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
@@ -106,8 +107,10 @@
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#endif
-#if defined(OS_WIN)
-#include "chrome/installer/util/browser_distribution.h"
+#if defined(OS_ANDROID)
+#include "chrome/browser/android/chrome_feature_list.h"
+#include "chrome/browser/ntp_snippets/ntp_snippets_service_factory.h"
+#include "components/ntp_snippets/ntp_snippets_service.h"
#endif
#if defined(OS_CHROMEOS)
@@ -1150,6 +1153,14 @@ void ProfileManager::DoFinalInitForServices(Profile* profile,
// migration code, rough time estimates are Q1 2016.
PasswordManagerSettingMigratorServiceFactory::GetForProfile(profile)
->InitializeMigration(ProfileSyncServiceFactory::GetForProfile(profile));
+
+#if defined(OS_ANDROID)
+ // Service is responsible for fetching content snippets for the NTP.
+ // Note: Create the service even if the feature is disabled, so that any
+ // remaining tasks will be cleaned up.
+ NTPSnippetsServiceFactory::GetForProfile(profile)->Init(
+ base::FeatureList::IsEnabled(chrome::android::kNTPSnippetsFeature));
+#endif
}
void ProfileManager::DoFinalInitLogging(Profile* profile) {
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 5dfafa9..d7aec55 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -837,6 +837,8 @@
'browser/android/ntp_snippets_bridge.h',
'browser/android/ntp_snippets_controller.cc',
'browser/android/ntp_snippets_controller.h',
+ 'browser/android/ntp_snippets_launcher.cc',
+ 'browser/android/ntp_snippets_launcher.h',
'browser/android/omnibox/answers_image_bridge.cc',
'browser/android/omnibox/answers_image_bridge.h',
'browser/android/omnibox/autocomplete_controller_android.cc',
@@ -1891,6 +1893,7 @@
'android/java/src/org/chromium/chrome/browser/ntp/RecentlyClosedBridge.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/ntp/snippets/SnippetsLauncher.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/ntp_snippets.gypi b/components/ntp_snippets.gypi
index 551d3e6..d6a0d63 100644
--- a/components/ntp_snippets.gypi
+++ b/components/ntp_snippets.gypi
@@ -22,6 +22,7 @@
'ntp_snippets/ntp_snippet.h',
'ntp_snippets/ntp_snippets_fetcher.cc',
'ntp_snippets/ntp_snippets_fetcher.h',
+ 'ntp_snippets/ntp_snippets_scheduler.h',
'ntp_snippets/ntp_snippets_service.cc',
'ntp_snippets/ntp_snippets_service.h',
],
diff --git a/components/ntp_snippets/ntp_snippets_scheduler.h b/components/ntp_snippets/ntp_snippets_scheduler.h
new file mode 100644
index 0000000..afa60d9
--- /dev/null
+++ b/components/ntp_snippets/ntp_snippets_scheduler.h
@@ -0,0 +1,32 @@
+// 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_SCHEDULER_H_
+#define COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_SCHEDULER_H_
+
+#include "base/macros.h"
+
+namespace ntp_snippets {
+
+// Interface to schedule the periodic fetching of snippets.
+class NTPSnippetsScheduler {
+ public:
+ // Schedule periodic fetching of snippets every |period_seconds|. The
+ // concrete implementation should call NTPSnippetsService::FetchSnippets once
+ // per period.
+ virtual bool Schedule(int period_seconds) = 0;
+
+ // Cancel the scheduled fetching task, if any.
+ virtual bool Unschedule() = 0;
+
+ protected:
+ NTPSnippetsScheduler() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NTPSnippetsScheduler);
+};
+
+} // namespace ntp_snippets
+
+#endif // COMPONENTS_NTP_SNIPPETS_NTP_SNIPPETS_SCHEDULER_H_
diff --git a/components/ntp_snippets/ntp_snippets_service.cc b/components/ntp_snippets/ntp_snippets_service.cc
index 149ab4a..fd031e3 100644
--- a/components/ntp_snippets/ntp_snippets_service.cc
+++ b/components/ntp_snippets/ntp_snippets_service.cc
@@ -12,7 +12,11 @@
#include "base/task_runner_util.h"
#include "base/values.h"
-namespace ntp_snippets {
+namespace {
+
+// TODO(crbug.com/587857): This is an extremely small value, for development.
+// Replace it by something sensible and add a command line param to control it.
+const int kDefaultFetchingIntervalSeconds = 60;
bool ReadFileToString(const base::FilePath& path, std::string* data) {
DCHECK(data);
@@ -21,13 +25,19 @@ bool ReadFileToString(const base::FilePath& path, std::string* data) {
return success;
}
+} // namespace
+
+namespace ntp_snippets {
+
NTPSnippetsService::NTPSnippetsService(
scoped_refptr<base::SequencedTaskRunner> file_task_runner,
const std::string& application_language_code,
+ NTPSnippetsScheduler* scheduler,
scoped_ptr<NTPSnippetsFetcher> snippets_fetcher)
: loaded_(false),
file_task_runner_(file_task_runner),
application_language_code_(application_language_code),
+ scheduler_(scheduler),
snippets_fetcher_(std::move(snippets_fetcher)),
weak_ptr_factory_(this) {
snippets_fetcher_callback_ = snippets_fetcher_->AddCallback(
@@ -37,6 +47,17 @@ NTPSnippetsService::NTPSnippetsService(
NTPSnippetsService::~NTPSnippetsService() {}
+void NTPSnippetsService::Init(bool enabled) {
+ // The scheduler only exists on Android so far, it's null on other platforms.
+ if (!scheduler_)
+ return;
+
+ if (enabled)
+ scheduler_->Schedule(kDefaultFetchingIntervalSeconds);
+ else
+ scheduler_->Unschedule();
+}
+
void NTPSnippetsService::Shutdown() {
FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_,
NTPSnippetsServiceShutdown(this));
diff --git a/components/ntp_snippets/ntp_snippets_service.h b/components/ntp_snippets/ntp_snippets_service.h
index f9d2a40..1b46bad 100644
--- a/components/ntp_snippets/ntp_snippets_service.h
+++ b/components/ntp_snippets/ntp_snippets_service.h
@@ -18,6 +18,7 @@
#include "components/ntp_snippets/inner_iterator.h"
#include "components/ntp_snippets/ntp_snippet.h"
#include "components/ntp_snippets/ntp_snippets_fetcher.h"
+#include "components/ntp_snippets/ntp_snippets_scheduler.h"
namespace ntp_snippets {
@@ -36,9 +37,12 @@ class NTPSnippetsService : public KeyedService, NTPSnippetsFetcher::Observer {
// (British english person in the US) are not language code.
NTPSnippetsService(scoped_refptr<base::SequencedTaskRunner> file_task_runner,
const std::string& application_language_code,
+ NTPSnippetsScheduler* scheduler,
scoped_ptr<NTPSnippetsFetcher> snippets_fetcher);
~NTPSnippetsService() override;
+ void Init(bool enabled);
+
// Fetches snippets from the server. |overwrite| is true if existing snippets
// should be overwritten.
void FetchSnippets(bool overwrite);
@@ -99,6 +103,9 @@ class NTPSnippetsService : public KeyedService, NTPSnippetsFetcher::Observer {
// The observers.
base::ObserverList<NTPSnippetsServiceObserver> observers_;
+ // Scheduler for fetching snippets. Not owned.
+ NTPSnippetsScheduler* scheduler_;
+
// The snippets fetcher
scoped_ptr<NTPSnippetsFetcher> snippets_fetcher_;
diff --git a/components/ntp_snippets/ntp_snippets_service_unittest.cc b/components/ntp_snippets/ntp_snippets_service_unittest.cc
index f801e94..c845b1b 100644
--- a/components/ntp_snippets/ntp_snippets_service_unittest.cc
+++ b/components/ntp_snippets/ntp_snippets_service_unittest.cc
@@ -61,7 +61,7 @@ class NTPSnippetsServiceTest : public testing::Test {
signin_client_.get(), account_tracker_.get());
scoped_ptr<NTPSnippetsService> service(
- new NTPSnippetsService(task_runner.get(), std::string("fr"),
+ new NTPSnippetsService(task_runner.get(), std::string("fr"), nullptr,
make_scoped_ptr(new NTPSnippetsFetcher(task_runner.get(),
signin_manager, token_service, request_context_getter,
base::FilePath()))));
@@ -75,7 +75,6 @@ class NTPSnippetsServiceTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(NTPSnippetsServiceTest);
};
-
TEST_F(NTPSnippetsServiceTest, Create) {
scoped_ptr<NTPSnippetsService> service(CreateSnippetService());
EXPECT_FALSE(service->is_loaded());
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 0602d85..319d1f8 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
@@ -54,13 +54,15 @@ IOSChromeNTPSnippetsServiceFactory::BuildServiceInstanceFor(
scoped_refptr<net::URLRequestContextGetter> request_context =
browser_state->GetRequestContext();
+ ntp_snippets::NTPSnippetsScheduler* scheduler = nullptr;
+
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(
- task_runner, GetApplicationContext()->GetApplicationLocale(),
+ task_runner, GetApplicationContext()->GetApplicationLocale(), scheduler,
make_scoped_ptr(new ntp_snippets::NTPSnippetsFetcher(
task_runner, (SigninManagerBase*)signin_manager, token_service,
request_context, browser_state->GetStatePath()))));