Commit c425234b authored by Justin DeWitt's avatar Justin DeWitt Committed by Commit Bot

[EoS] Implement background scheduler task for Explore Sites updates.

This implements a periodic background task that attempts to fetch the
Explore Sites catalog approximately daily.  Fetch implementation is left
to a future CL, as is initiating the background task.

Bug: 867488
Change-Id: I808a7bacd9f20c3c1dbcaf84acd413eb7353c644
Reviewed-on: https://chromium-review.googlesource.com/1236506Reviewed-by: default avatarFilip Gorski <fgorski@chromium.org>
Commit-Queue: Justin DeWitt <dewittj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#593071}
parent ca724afa
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.explore_sites;
import android.content.Context;
import org.chromium.base.ContextUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask.StartBeforeNativeResult;
import org.chromium.chrome.browser.offlinepages.DeviceConditions;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.background_task_scheduler.BackgroundTask.TaskFinishedCallback;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.background_task_scheduler.TaskIds;
import org.chromium.components.background_task_scheduler.TaskInfo;
import org.chromium.components.background_task_scheduler.TaskParameters;
import org.chromium.net.ConnectionType;
import java.util.concurrent.TimeUnit;
/**
* Schedules and executes background update of the Explore Sites catalog.
*
* The catalog is updated on a somewhat slow schedule, but this code checks for updates
* approximately daily so that freshness is maintained.
*/
@JNINamespace("explore_sites")
public class ExploreSitesBackgroundTask extends NativeBackgroundTask {
public static final int DEFAULT_DELAY_HOURS = 25;
public static final int DEFAULT_FLEX_HOURS = 2;
private TaskFinishedCallback mTaskFinishedCallback;
private Profile mProfile;
public ExploreSitesBackgroundTask() {}
@VisibleForTesting
protected Profile getProfile() {
if (mProfile == null) mProfile = Profile.getLastUsedProfile();
return mProfile;
}
@Override
public @StartBeforeNativeResult int onStartTaskBeforeNativeLoaded(
Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
@ConnectionType
int connectionType = DeviceConditions.getCurrentNetConnectionType(context);
if (connectionType == ConnectionType.CONNECTION_NONE) {
return StartBeforeNativeResult.RESCHEDULE;
}
return StartBeforeNativeResult.LOAD_NATIVE;
}
@Override
protected void onStartTaskWithNative(
Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
assert taskParameters.getTaskId() == TaskIds.EXPLORE_SITES_REFRESH_JOB_ID;
mTaskFinishedCallback = callback;
ExploreSitesBridge.updateCatalogFromNetwork(
getProfile(), (ignored) -> mTaskFinishedCallback.taskFinished(false));
}
@Override
protected boolean onStopTaskBeforeNativeLoaded(Context context, TaskParameters taskParameters) {
// Don't retry, this task is periodic anyways.
return false;
}
@Override
protected boolean onStopTaskWithNative(Context context, TaskParameters taskParameters) {
// Don't retry, this task is periodic anyways.
return false;
}
@Override
public void reschedule(Context context) {
schedule();
}
// Removes the task from the JobScheduler queue. Should be called when the feature is disabled.
public static void cancelTask() {
BackgroundTaskSchedulerFactory.getScheduler().cancel(
ContextUtils.getApplicationContext(), TaskIds.EXPLORE_SITES_REFRESH_JOB_ID);
}
// Begins the periodic update task.
public static void schedule() {
TaskInfo.Builder taskInfoBuilder =
TaskInfo.createPeriodicTask(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID,
ExploreSitesBackgroundTask.class,
TimeUnit.HOURS.toMillis(DEFAULT_DELAY_HOURS),
TimeUnit.HOURS.toMillis(DEFAULT_FLEX_HOURS))
.setRequiredNetworkType(TaskInfo.NETWORK_TYPE_ANY)
.setIsPersisted(true)
.setUpdateCurrent(true);
BackgroundTaskSchedulerFactory.getScheduler().schedule(
ContextUtils.getApplicationContext(), taskInfoBuilder.build());
}
}
......@@ -29,6 +29,14 @@ public class ExploreSitesBridge {
nativeGetEspCatalog(profile, result, callback);
}
/**
* Causes a network request for updating the catalog.
*/
public static void updateCatalogFromNetwork(Profile profile, Callback<Void> finishedCallback) {
// TODO(dewittj): Connect to the ExploreSitesService on the native side.
finishedCallback.onResult(null);
}
private static native void nativeGetEspCatalog(Profile profile,
List<ExploreSitesCategory> result, Callback<List<ExploreSitesCategory>> callback);
}
......@@ -557,6 +557,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/engagement/SiteEngagementService.java",
"java/src/org/chromium/chrome/browser/explore_sites/ExperimentalExploreSitesCategoryTileView.java",
"java/src/org/chromium/chrome/browser/explore_sites/ExperimentalExploreSitesSection.java",
"java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTask.java",
"java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridge.java",
"java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBridgeExperimental.java",
"java/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategory.java",
......@@ -2201,6 +2202,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/download/home/list/ShareUtilsTest.java",
"junit/src/org/chromium/chrome/browser/download/items/OfflineContentAggregatorNotificationBridgeUiTest.java",
"junit/src/org/chromium/chrome/browser/download/DownloadResumptionSchedulerTest.java",
"junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesBackgroundTaskUnitTest.java",
"junit/src/org/chromium/chrome/browser/explore_sites/ExploreSitesCategoryUnitTest.java",
"junit/src/org/chromium/chrome/browser/externalauth/ExternalAuthUtilsTest.java",
"junit/src/org/chromium/chrome/browser/feedback/FeedbackCollectorTest.java",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.explore_sites;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import android.content.Context;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
import org.mockito.invocation.InvocationOnMock;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadows.multidex.ShadowMultiDex;
import org.chromium.base.Callback;
import org.chromium.base.library_loader.ProcessInitException;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
import org.chromium.chrome.browser.init.BrowserParts;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.offlinepages.DeviceConditions;
import org.chromium.chrome.browser.offlinepages.ShadowDeviceConditions;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.background_task_scheduler.BackgroundTaskScheduler;
import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerFactory;
import org.chromium.components.background_task_scheduler.TaskIds;
import org.chromium.components.background_task_scheduler.TaskInfo;
import org.chromium.components.background_task_scheduler.TaskParameters;
import org.chromium.net.ConnectionType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link ExploreSitesBackgroundTask}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows = {ShadowMultiDex.class, ShadowDeviceConditions.class,
ExploreSitesBackgroundTaskUnitTest.ShadowExploreSitesBridge.class})
public class ExploreSitesBackgroundTaskUnitTest {
/** Implementation of ExploreSitesBridge which does not rely on native. */
@Implements(ExploreSitesBridge.class)
public static class ShadowExploreSitesBridge {
public static Callback<Void> mUpdateCatalogFinishedCallback;
@Implementation
public static void getEspCatalog(
Profile profile, Callback<List<ExploreSitesCategory>> callback) {}
@Implementation
public static void updateCatalogFromNetwork(
Profile profile, Callback<Void> finishedCallback) {
mUpdateCatalogFinishedCallback = finishedCallback;
}
}
/**
* Fake of BackgroundTaskScheduler system service.
*/
public static class FakeBackgroundTaskScheduler implements BackgroundTaskScheduler {
private HashMap<Integer, TaskInfo> mTaskInfos = new HashMap<>();
@Override
public boolean schedule(Context context, TaskInfo taskInfo) {
mTaskInfos.put(taskInfo.getTaskId(), taskInfo);
return true;
}
@Override
public void cancel(Context context, int taskId) {
mTaskInfos.remove(taskId);
}
@Override
public void checkForOSUpgrade(Context context) {}
@Override
public void reschedule(Context context) {}
public TaskInfo getTaskInfo(int taskId) {
return mTaskInfos.get(taskId);
}
}
void initDeviceConditions(@ConnectionType int connectionType) {
boolean powerConnected = true;
boolean powerSaveModeOn = true;
int highBatteryLevel = 75;
boolean metered = true;
DeviceConditions deviceConditions = new DeviceConditions(
!powerConnected, highBatteryLevel, connectionType, !powerSaveModeOn, !metered);
ShadowDeviceConditions.setCurrentConditions(deviceConditions);
}
@Spy
private ExploreSitesBackgroundTask mExploreSitesBackgroundTask =
new ExploreSitesBackgroundTask();
@Mock
private ChromeBrowserInitializer mChromeBrowserInitializer;
@Captor
ArgumentCaptor<BrowserParts> mBrowserParts;
private FakeBackgroundTaskScheduler mFakeTaskScheduler;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doNothing().when(mChromeBrowserInitializer).handlePreNativeStartup(any(BrowserParts.class));
try {
doAnswer((InvocationOnMock invocation) -> {
mBrowserParts.getValue().finishNativeInitialization();
return null;
})
.when(mChromeBrowserInitializer)
.handlePostNativeStartup(eq(true), mBrowserParts.capture());
} catch (ProcessInitException ex) {
fail("Unexpected exception while initializing mock of ChromeBrowserInitializer.");
}
ChromeBrowserInitializer.setForTesting(mChromeBrowserInitializer);
mFakeTaskScheduler = new FakeBackgroundTaskScheduler();
BackgroundTaskSchedulerFactory.setSchedulerForTesting(mFakeTaskScheduler);
doReturn(null).when(mExploreSitesBackgroundTask).getProfile();
}
@Test
public void scheduleTask() {
ExploreSitesBackgroundTask.schedule();
TaskInfo scheduledTask =
mFakeTaskScheduler.getTaskInfo(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID);
assertNotNull(scheduledTask);
assertEquals(TimeUnit.HOURS.toMillis(ExploreSitesBackgroundTask.DEFAULT_DELAY_HOURS),
scheduledTask.getPeriodicInfo().getIntervalMs());
assertEquals(true, scheduledTask.isPersisted());
assertEquals(TaskInfo.NETWORK_TYPE_ANY, scheduledTask.getRequiredNetworkType());
}
@Test
public void cancelTask() {
TaskInfo scheduledTask =
mFakeTaskScheduler.getTaskInfo(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID);
assertNull(scheduledTask);
ExploreSitesBackgroundTask.schedule();
scheduledTask = mFakeTaskScheduler.getTaskInfo(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID);
assertNotNull(scheduledTask);
assertEquals(TimeUnit.HOURS.toMillis(ExploreSitesBackgroundTask.DEFAULT_DELAY_HOURS),
scheduledTask.getPeriodicInfo().getIntervalMs());
ExploreSitesBackgroundTask.cancelTask();
scheduledTask = mFakeTaskScheduler.getTaskInfo(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID);
assertNull(scheduledTask);
}
@Test
public void testNoNetwork() throws Exception {
initDeviceConditions(ConnectionType.CONNECTION_NONE);
TaskParameters params = TaskParameters.create(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID).build();
int result = mExploreSitesBackgroundTask.onStartTaskBeforeNativeLoaded(
RuntimeEnvironment.application, params, (boolean needsReschedule) -> {
fail("Finished callback should not be run, network conditions not met.");
});
assertEquals(NativeBackgroundTask.StartBeforeNativeResult.RESCHEDULE, result);
}
@Test
public void testWithNetwork() throws Exception {
initDeviceConditions(ConnectionType.CONNECTION_2G);
TaskParameters params = TaskParameters.create(TaskIds.EXPLORE_SITES_REFRESH_JOB_ID).build();
final ArrayList<Boolean> taskFinishedList = new ArrayList<>();
mExploreSitesBackgroundTask.onStartTask(RuntimeEnvironment.application, params,
(boolean needsReschedule) -> { taskFinishedList.add(needsReschedule); });
// Simulate update finishing from the native side.
ShadowExploreSitesBridge.mUpdateCatalogFinishedCallback.onResult(null);
assertEquals(1, taskFinishedList.size());
assertEquals(false, taskFinishedList.get(0));
}
}
......@@ -33,8 +33,9 @@ class BackgroundTaskSchedulerUma {
static final int BACKGROUND_TASK_DOWNLOAD_RESUMPTION = 13;
static final int BACKGROUND_TASK_FEED_REFRESH = 14;
static final int BACKGROUND_TASK_COMPONENT_UPDATE = 15;
static final int BACKGROUND_TASK_EXPLORE_SITES_REFRESH = 16;
// Keep this one at the end and increment appropriately when adding new tasks.
static final int BACKGROUND_TASK_COUNT = 16;
static final int BACKGROUND_TASK_COUNT = 17;
static final String KEY_CACHED_UMA = "bts_cached_uma";
......@@ -253,6 +254,8 @@ class BackgroundTaskSchedulerUma {
return BACKGROUND_TASK_FEED_REFRESH;
case TaskIds.COMPONENT_UPDATE_JOB_ID:
return BACKGROUND_TASK_COMPONENT_UPDATE;
case TaskIds.EXPLORE_SITES_REFRESH_JOB_ID:
return BACKGROUND_TASK_EXPLORE_SITES_REFRESH;
default:
assert false;
}
......
......@@ -28,6 +28,7 @@ public final class TaskIds {
public static final int DOWNLOAD_RESUMPTION_JOB_ID = 55;
public static final int FEED_REFRESH_JOB_ID = 22;
public static final int COMPONENT_UPDATE_JOB_ID = 2;
public static final int EXPLORE_SITES_REFRESH_JOB_ID = 100;
private TaskIds() {}
}
......@@ -87,7 +87,10 @@ public class BackgroundTaskSchedulerUmaTest {
assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COMPONENT_UPDATE,
BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
TaskIds.COMPONENT_UPDATE_JOB_ID));
assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 16);
assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_EXPLORE_SITES_REFRESH,
BackgroundTaskSchedulerUma.toUmaEnumValueFromTaskId(
TaskIds.EXPLORE_SITES_REFRESH_JOB_ID));
assertEquals(BackgroundTaskSchedulerUma.BACKGROUND_TASK_COUNT, 17);
}
@Test
......
......@@ -3244,6 +3244,8 @@ uploading your change for review. These are checked by presubmit scripts.
<int value="12" label="WebAPK update task"/>
<int value="13" label="Download resumption task"/>
<int value="14" label="Feed refresh task"/>
<int value="15" label="Component update task"/>
<int value="16" label="Explore Sites refresh task"/>
</enum>
<enum name="BackgroundTracingState">
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment