0

[NTP Snippets] Scheduler: Fetch on Wifi (without charging) only 6AM-10PM

BUG=587857

Review URL: https://codereview.chromium.org/1869323003

Cr-Commit-Position: refs/heads/master@{#388231}
This commit is contained in:
treib
2016-04-19 10:26:13 -07:00
committed by Commit bot
parent 9d5e1f2d8f
commit e59dc80836
10 changed files with 223 additions and 59 deletions

@ -53,6 +53,10 @@ public class ChromeBackgroundService extends GcmTaskService {
handleFetchSnippets(context);
break;
case SnippetsLauncher.TASK_TAG_RESCHEDULE:
handleRescheduleSnippets(context);
break;
case PrecacheController.PERIODIC_TASK_TAG:
case PrecacheController.CONTINUATION_TASK_TAG:
handlePrecache(context, params.getTag());
@ -91,6 +95,18 @@ public class ChromeBackgroundService extends GcmTaskService {
SnippetsBridge.fetchSnippets();
}
private void handleRescheduleSnippets(Context context) {
if (!SnippetsLauncher.hasInstance()) {
launchBrowser(context);
}
rescheduleSnippets();
}
@VisibleForTesting
protected void rescheduleSnippets() {
SnippetsBridge.rescheduleFetching();
}
private void handlePrecache(Context context, String tag) {
if (!hasPrecacheInstance()) {
launchBrowser(context);

@ -53,6 +53,14 @@ public class SnippetsBridge {
nativeFetchSnippets();
}
/**
* Reschedules the fetching of snippets. Used to support different fetching intervals for
* different times of day.
*/
public static void rescheduleFetching() {
nativeRescheduleFetching();
}
/**
* Tells the native service to discard a snippet. It will be removed from the native side
* storage and will also be discarded from subsequent fetch results.
@ -98,6 +106,7 @@ public class SnippetsBridge {
private native long nativeInit(Profile profile);
private native void nativeDestroy(long nativeNTPSnippetsBridge);
private static native void nativeFetchSnippets();
private static native void nativeRescheduleFetching();
private native void nativeDiscardSnippet(long nativeNTPSnippetsBridge, String snippetUrl);
private native void nativeSetObserver(long nativeNTPSnippetsBridge, SnippetsBridge bridge);
}

@ -7,6 +7,7 @@ package org.chromium.chrome.browser.ntp.snippets;
import android.content.Context;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.OneoffTask;
import com.google.android.gms.gcm.PeriodicTask;
import com.google.android.gms.gcm.Task;
@ -18,6 +19,8 @@ import org.chromium.chrome.browser.ChromeBackgroundService;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.externalauth.UserRecoverableErrorHandler;
import java.util.Date;
/**
* The {@link SnippetsLauncher} singleton is created and owned by the C++ browser.
*
@ -26,10 +29,15 @@ import org.chromium.chrome.browser.externalauth.UserRecoverableErrorHandler;
public class SnippetsLauncher {
private static final String TAG = "SnippetsLauncher";
// Task tags for fetching snippets.
public static final String TASK_TAG_WIFI_CHARGING = "FetchSnippetsWifiCharging";
public static final String TASK_TAG_WIFI = "FetchSnippetsWifi";
public static final String TASK_TAG_FALLBACK = "FetchSnippetsFallback";
// Task tag for re-scheduling the snippet fetching. This is used to support different fetching
// intervals during different times of day.
public static final String TASK_TAG_RESCHEDULE = "RescheduleSnippets";
// 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;
@ -90,7 +98,7 @@ public class SnippetsLauncher {
}
}
private static PeriodicTask buildTask(
private static PeriodicTask buildFetchTask(
String tag, long periodSeconds, int requiredNetwork, boolean requiresCharging) {
return new PeriodicTask.Builder()
.setService(ChromeBackgroundService.class)
@ -103,21 +111,51 @@ public class SnippetsLauncher {
.build();
}
private static OneoffTask buildRescheduleTask(Date date) {
Date now = new Date();
// Convert from milliseconds to seconds, rounding up.
long delaySeconds = (now.getTime() - date.getTime() + 999) / 1000;
final long intervalSeconds = 15 * 60;
return new OneoffTask.Builder()
.setService(ChromeBackgroundService.class)
.setTag(TASK_TAG_RESCHEDULE)
.setExecutionWindow(delaySeconds, delaySeconds + intervalSeconds)
.setRequiredNetwork(Task.NETWORK_STATE_ANY)
.setRequiresCharging(false)
.setPersisted(true)
.setUpdateCurrent(true)
.build();
}
private void scheduleOrCancelFetchTask(
String taskTag, long period, int requiredNetwork, boolean requiresCharging) {
if (period > 0) {
mScheduler.schedule(buildFetchTask(taskTag, period, requiredNetwork, requiresCharging));
} else {
mScheduler.cancelTask(taskTag, ChromeBackgroundService.class);
}
}
@CalledByNative
private boolean schedule(
long periodWifiChargingSeconds, long periodWifiSeconds, long periodFallbackSeconds) {
private boolean schedule(long periodWifiChargingSeconds, long periodWifiSeconds,
long periodFallbackSeconds, long rescheduleTime) {
if (!mGCMEnabled) return false;
Log.d(TAG, "Scheduling: " + periodWifiChargingSeconds + " " + periodWifiSeconds + " "
+ periodFallbackSeconds);
// 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.
try {
mScheduler.schedule(buildTask(TASK_TAG_WIFI_CHARGING, periodWifiChargingSeconds,
Task.NETWORK_STATE_UNMETERED, true));
mScheduler.schedule(buildTask(
TASK_TAG_WIFI, periodWifiSeconds, Task.NETWORK_STATE_UNMETERED, false));
mScheduler.schedule(buildTask(
TASK_TAG_FALLBACK, periodFallbackSeconds, Task.NETWORK_STATE_CONNECTED, false));
scheduleOrCancelFetchTask(TASK_TAG_WIFI_CHARGING, periodWifiChargingSeconds,
Task.NETWORK_STATE_UNMETERED, true);
scheduleOrCancelFetchTask(
TASK_TAG_WIFI, periodWifiSeconds, Task.NETWORK_STATE_UNMETERED, false);
scheduleOrCancelFetchTask(
TASK_TAG_FALLBACK, periodFallbackSeconds, Task.NETWORK_STATE_CONNECTED, false);
if (rescheduleTime > 0) {
mScheduler.schedule(buildRescheduleTask(new Date(rescheduleTime)));
} else {
mScheduler.cancelTask(TASK_TAG_RESCHEDULE, ChromeBackgroundService.class);
}
} catch (IllegalArgumentException e) {
// Disable GCM for the remainder of this session.
mGCMEnabled = false;
@ -131,18 +169,7 @@ public class SnippetsLauncher {
private boolean unschedule() {
if (!mGCMEnabled) return false;
Log.i(TAG, "Unscheduling");
try {
mScheduler.cancelTask(TASK_TAG_WIFI_CHARGING, ChromeBackgroundService.class);
mScheduler.cancelTask(TASK_TAG_WIFI, ChromeBackgroundService.class);
mScheduler.cancelTask(TASK_TAG_FALLBACK, 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;
return schedule(0, 0, 0, 0);
}
}

@ -29,6 +29,7 @@ public class ChromeBackgroundServiceTest extends InstrumentationTestCase {
static class MockTaskService extends ChromeBackgroundService {
private boolean mDidLaunchBrowser = false;
private boolean mDidFetchSnippets = false;
private boolean mDidRescheduleSnippets = false;
private boolean mHasPrecacheInstance = true;
private boolean mPrecachingStarted = false;
@ -42,6 +43,11 @@ public class ChromeBackgroundServiceTest extends InstrumentationTestCase {
mDidFetchSnippets = true;
}
@Override
protected void rescheduleSnippets() {
mDidRescheduleSnippets = true;
}
@Override
protected boolean hasPrecacheInstance() {
return mHasPrecacheInstance;
@ -58,13 +64,16 @@ public class ChromeBackgroundServiceTest extends InstrumentationTestCase {
// 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,
final boolean expectedPrecacheStarted, final boolean expectedFetchSnippets) {
final boolean expectedPrecacheStarted, final boolean expectedFetchSnippets,
final boolean expectedRescheduleSnippets) {
ThreadUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
assertEquals("StartedService", expectedLaunchBrowser, mDidLaunchBrowser);
assertEquals("StartedPrecache", expectedPrecacheStarted, mPrecachingStarted);
assertEquals("FetchedSnippets", expectedFetchSnippets, mDidFetchSnippets);
assertEquals("RescheduledSnippets", expectedRescheduleSnippets,
mDidRescheduleSnippets);
}
});
}
@ -95,51 +104,87 @@ public class ChromeBackgroundServiceTest extends InstrumentationTestCase {
}
private void startOnRunTaskAndVerify(String taskTag, boolean shouldStart,
boolean shouldPrecache, boolean shouldFetchSnippets) {
boolean shouldPrecache, boolean shouldFetchSnippets, boolean shouldRescheduleSnippets) {
mTaskService.onRunTask(new TaskParams(taskTag));
mTaskService.checkExpectations(shouldStart, shouldPrecache, shouldFetchSnippets);
mTaskService.checkExpectations(
shouldStart, shouldPrecache, shouldFetchSnippets, shouldRescheduleSnippets);
}
@SmallTest
@Feature({"BackgroundSync"})
public void testBackgroundSyncNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, false, false, false);
startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, false, false, false, false);
}
@SmallTest
@Feature({"BackgroundSync"})
public void testBackgroundSyncLaunchBrowserWhenInstanceDoesNotExist() {
deleteSyncLauncherInstance();
startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, true, false, false);
startOnRunTaskAndVerify(BackgroundSyncLauncher.TASK_TAG, true, false, false, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI_CHARGING, false, false, true);
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, false, false, true);
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, false, false, true);
public void testNTPSnippetsFetchWifiChargingNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI_CHARGING, false, false, true, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsLaunchBrowserWhenInstanceDoesNotExist() {
public void testNTPSnippetsFetchWifiNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, false, false, true, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsFetchFallbackNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, false, false, true, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsRescheduleNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_RESCHEDULE, false, false, false, true);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsFetchWifiChargingLaunchBrowserWhenInstanceDoesNotExist() {
deleteSnippetsLauncherInstance();
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI_CHARGING, true, false, true);
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, true, false, true);
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, true, false, true);
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI_CHARGING, true, false, true, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsFetchWifiLaunchBrowserWhenInstanceDoesNotExist() {
deleteSnippetsLauncherInstance();
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_WIFI, true, false, true, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsFetchFallbackLaunchBrowserWhenInstanceDoesNotExist() {
deleteSnippetsLauncherInstance();
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_FALLBACK, true, false, true, false);
}
@SmallTest
@Feature({"NTPSnippets"})
public void testNTPSnippetsRescheduleLaunchBrowserWhenInstanceDoesNotExist() {
deleteSnippetsLauncherInstance();
startOnRunTaskAndVerify(SnippetsLauncher.TASK_TAG_RESCHEDULE, true, false, false, true);
}
@SmallTest
@Feature({"Precache"})
public void testPrecacheNoLaunchBrowserWhenInstanceExists() {
startOnRunTaskAndVerify(PrecacheController.PERIODIC_TASK_TAG, false, false, false);
startOnRunTaskAndVerify(PrecacheController.PERIODIC_TASK_TAG, false, false, false, false);
}
@SmallTest
@Feature({"Precache"})
public void testPrecacheLaunchBrowserWhenInstanceDoesNotExist() {
mTaskService.deletePrecacheInstance();
startOnRunTaskAndVerify(PrecacheController.PERIODIC_TASK_TAG, true, true, false);
startOnRunTaskAndVerify(PrecacheController.PERIODIC_TASK_TAG, true, true, false, false);
}
}

@ -36,6 +36,14 @@ static void FetchSnippets(JNIEnv* env,
NTPSnippetsServiceFactory::GetForProfile(profile)->FetchSnippets();
}
// Reschedules the fetching of snippets. Used to support different fetching
// intervals for different times of day.
static void RescheduleFetching(JNIEnv* env,
const JavaParamRef<jclass>& caller) {
Profile* profile = ProfileManager::GetLastUsedProfile();
NTPSnippetsServiceFactory::GetForProfile(profile)->RescheduleFetching();
}
NTPSnippetsBridge::NTPSnippetsBridge(JNIEnv* env,
const JavaParamRef<jobject>& j_profile)
: snippet_service_observer_(this) {

@ -30,13 +30,15 @@ bool NTPSnippetsLauncher::Register(JNIEnv* env) {
bool NTPSnippetsLauncher::Schedule(base::TimeDelta period_wifi_charging,
base::TimeDelta period_wifi,
base::TimeDelta period_fallback) {
base::TimeDelta period_fallback,
base::Time reschedule_time) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
JNIEnv* env = base::android::AttachCurrentThread();
return Java_SnippetsLauncher_schedule(
env, java_launcher_.obj(), period_wifi_charging.InSeconds(),
period_wifi.InSeconds(), period_fallback.InSeconds());
period_wifi.InSeconds(), period_fallback.InSeconds(),
reschedule_time.ToJavaTime());
}
bool NTPSnippetsLauncher::Unschedule() {

@ -23,7 +23,8 @@ class NTPSnippetsLauncher : public ntp_snippets::NTPSnippetsScheduler {
// ntp_snippets::NTPSnippetsScheduler implementation.
bool Schedule(base::TimeDelta period_wifi_charging,
base::TimeDelta period_wifi,
base::TimeDelta period_fallback) override;
base::TimeDelta period_fallback,
base::Time reschedule_time) override;
bool Unschedule() override;
private:

@ -14,13 +14,18 @@ namespace ntp_snippets {
class NTPSnippetsScheduler {
public:
// Schedule periodic fetching of snippets, with different period depending on
// network and charging state. The concrete implementation should call
// NTPSnippetsService::FetchSnippets once per period.
// network and charging state, and also set up a delay after which the periods
// may change. The concrete implementation should call
// NTPSnippetsService::FetchSnippets once per period, and
// NTPSnippetsService::RescheduleFetching at |reschedule_time|.
// Any of the values can be zero to indicate that the corresponding task
// should not be scheduled.
virtual bool Schedule(base::TimeDelta period_wifi_charging,
base::TimeDelta period_wifi,
base::TimeDelta period_fallback) = 0;
base::TimeDelta period_fallback,
base::Time reschedule_time) = 0;
// Cancel the scheduled fetching task, if any.
// Cancel any scheduled tasks.
virtual bool Unschedule() = 0;
protected:

@ -35,6 +35,11 @@ const int kFetchingIntervalWifiChargingSeconds = 30 * 60;
const int kFetchingIntervalWifiSeconds = 2 * 60 * 60;
const int kFetchingIntervalFallbackSeconds = 24 * 60 * 60;
// These define the times of day during which we will fetch via Wifi (without
// charging) - 6 AM to 10 PM.
const int kWifiFetchingHourMin = 6;
const int kWifiFetchingHourMax = 22;
const int kDefaultExpiryTimeMins = 24 * 60;
base::TimeDelta GetFetchingInterval(const char* switch_name,
@ -57,9 +62,16 @@ base::TimeDelta GetFetchingIntervalWifiCharging() {
kFetchingIntervalWifiChargingSeconds);
}
base::TimeDelta GetFetchingIntervalWifi() {
return GetFetchingInterval(switches::kFetchingIntervalWifiSeconds,
kFetchingIntervalWifiSeconds);
base::TimeDelta GetFetchingIntervalWifi(const base::Time& now) {
// Only fetch via Wifi (without charging) during the proper times of day.
base::Time::Exploded exploded;
now.LocalExplode(&exploded);
if (kWifiFetchingHourMin <= exploded.hour &&
exploded.hour < kWifiFetchingHourMax) {
return GetFetchingInterval(switches::kFetchingIntervalWifiSeconds,
kFetchingIntervalWifiSeconds);
}
return base::TimeDelta();
}
base::TimeDelta GetFetchingIntervalFallback() {
@ -67,6 +79,31 @@ base::TimeDelta GetFetchingIntervalFallback() {
kFetchingIntervalFallbackSeconds);
}
base::Time GetRescheduleTime(const base::Time& now) {
base::Time::Exploded exploded;
now.LocalExplode(&exploded);
// The scheduling changes at both |kWifiFetchingHourMin| and
// |kWifiFetchingHourMax|. Find the time of the next one that we'll hit.
bool next_day = false;
if (exploded.hour < kWifiFetchingHourMin) {
exploded.hour = kWifiFetchingHourMin;
} else if (exploded.hour < kWifiFetchingHourMax) {
exploded.hour = kWifiFetchingHourMax;
} else {
next_day = true;
exploded.hour = kWifiFetchingHourMin;
}
// In any case, reschedule at the full hour.
exploded.minute = 0;
exploded.second = 0;
exploded.millisecond = 0;
base::Time reschedule = base::Time::FromLocalExploded(exploded);
if (next_day)
reschedule += base::TimeDelta::FromDays(1);
return reschedule;
}
// Extracts the hosts from |suggestions| and returns them in a set.
std::set<std::string> GetSuggestionsHostsImpl(
const SuggestionsProfile& suggestions) {
@ -124,7 +161,8 @@ NTPSnippetsService::NTPSnippetsService(
NTPSnippetsScheduler* scheduler,
scoped_ptr<NTPSnippetsFetcher> snippets_fetcher,
const ParseJSONCallback& parse_json_callback)
: pref_service_(pref_service),
: enabled_(false),
pref_service_(pref_service),
suggestions_service_(suggestions_service),
file_task_runner_(file_task_runner),
application_language_code_(application_language_code),
@ -146,7 +184,8 @@ void NTPSnippetsService::RegisterProfilePrefs(PrefRegistrySimple* registry) {
}
void NTPSnippetsService::Init(bool enabled) {
if (enabled) {
enabled_ = enabled;
if (enabled_) {
// |suggestions_service_| can be null in tests.
if (suggestions_service_) {
suggestions_service_subscription_ = suggestions_service_->AddCallback(
@ -163,22 +202,13 @@ void NTPSnippetsService::Init(bool enabled) {
FetchSnippets();
}
// The scheduler only exists on Android so far, it's null on other platforms.
if (!scheduler_)
return;
if (enabled) {
scheduler_->Schedule(GetFetchingIntervalWifiCharging(),
GetFetchingIntervalWifi(),
GetFetchingIntervalFallback());
} else {
scheduler_->Unschedule();
}
RescheduleFetching();
}
void NTPSnippetsService::Shutdown() {
FOR_EACH_OBSERVER(NTPSnippetsServiceObserver, observers_,
NTPSnippetsServiceShutdown());
enabled_ = false;
}
void NTPSnippetsService::FetchSnippets() {
@ -196,6 +226,21 @@ void NTPSnippetsService::FetchSnippetsFromHosts(
snippets_fetcher_->FetchSnippets(hosts);
}
void NTPSnippetsService::RescheduleFetching() {
// The scheduler only exists on Android so far, it's null on other platforms.
if (!scheduler_)
return;
if (enabled_) {
base::Time now = base::Time::Now();
scheduler_->Schedule(
GetFetchingIntervalWifiCharging(), GetFetchingIntervalWifi(now),
GetFetchingIntervalFallback(), GetRescheduleTime(now));
} else {
scheduler_->Unschedule();
}
}
void NTPSnippetsService::ClearSnippets() {
snippets_.clear();

@ -79,6 +79,10 @@ class NTPSnippetsService : public KeyedService {
// suggestions from the suggestion service) and adds them to the current ones.
void FetchSnippetsFromHosts(const std::set<std::string>& hosts);
// (Re)schedules the periodic fetching of snippets. This is necessary because
// the schedule depends on the time of day
void RescheduleFetching();
// Deletes all currently stored snippets.
void ClearSnippets();
@ -148,6 +152,8 @@ class NTPSnippetsService : public KeyedService {
void RemoveExpiredSnippets();
bool enabled_;
PrefService* pref_service_;
suggestions::SuggestionsService* suggestions_service_;