diff options
| author | treib <treib@chromium.org> | 2016-02-25 07:44:40 -0800 |
|---|---|---|
| committer | Commit bot <commit-bot@chromium.org> | 2016-02-25 15:45:53 +0000 |
| commit | e6aa5c3359e84aea9a4be25dff250a4ca3fef35e (patch) | |
| tree | ba64846463f698da47538affc1e019b8f6bca234 | |
| parent | 438cbb9ca139d96fb82211cb87fef4c072e29b7b (diff) | |
| download | chromium_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}
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())))); |
