[BackgroundSync] Use BackgroundTaskScheduler.

Use BackgroundTaskScheduler to wake up Chrome for Android, if the
newly introduced feature flag is enabled.

Bug: 924490
Change-Id: Iaa42ff2c10af4fbb466e8d892ccf626ee1827cd6
Reviewed-on: https://chromium-review.googlesource.com/c/1432840
Commit-Queue: Mugdha Lakhani <[email protected]>
Reviewed-by: Peter Beverloo <[email protected]>
Reviewed-by: Rayan Kanso <[email protected]>
Reviewed-by: David Trainor <[email protected]>
Reviewed-by: Ilya Sherman <[email protected]>
Cr-Commit-Position: refs/heads/master@{#630285}
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 847bbbe..c9082077 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java
@@ -105,8 +105,7 @@
             protected void onPostExecute(Boolean shouldLaunch) {
                 callback.run(shouldLaunch);
             }
-        }
-                .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
     /**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
index d34b694..47c1ebe 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeBackgroundService.java
@@ -14,6 +14,7 @@
 import org.chromium.base.ThreadUtils;
 import org.chromium.base.VisibleForTesting;
 import org.chromium.base.library_loader.ProcessInitException;
+import org.chromium.chrome.browser.background_sync.BackgroundSyncBackgroundTaskScheduler;
 import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
 import org.chromium.chrome.browser.init.ServiceManagerStartupUtils;
 import org.chromium.chrome.browser.ntp.snippets.SnippetsBridge;
@@ -70,6 +71,14 @@
     }
 
     private void handleBackgroundSyncEvent(Context context, String tag) {
+        if (ChromeFeatureList.isEnabled(
+                    ChromeFeatureList.BACKGROUND_TASK_SCHEDULER_FOR_BACKGROUND_SYNC)) {
+            // If BackgroundTaskScheduler has been used to schedule a background
+            // task for Background Sync, we simply reschedule the existing task(s).
+            BackgroundSyncBackgroundTaskScheduler.getInstance().reschedule();
+            return;
+        }
+
         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
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index 90ccb6b..e938ff6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -168,6 +168,8 @@
     public static final String AUTOFILL_MANUAL_FALLBACK_ANDROID = "AutofillManualFallbackAndroid";
     public static final String AUTOFILL_REFRESH_STYLE_ANDROID = "AutofillRefreshStyleAndroid";
     public static final String AUTOFILL_KEYBOARD_ACCESSORY = "AutofillKeyboardAccessory";
+    public static final String BACKGROUND_TASK_SCHEDULER_FOR_BACKGROUND_SYNC =
+            "BackgroundTaskSchedulerForBackgroundSync";
     public static final String CAPTIVE_PORTAL_CERTIFICATE_LIST = "CaptivePortalCertificateList";
     public static final String CCT_BACKGROUND_TAB = "CCTBackgroundTab";
     public static final String CCT_MODULE = "CCTModule";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java
new file mode 100644
index 0000000..674d4ba
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java
@@ -0,0 +1,76 @@
+// Copyright 2019 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.background_sync;
+
+import android.content.Context;
+
+import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.background_task_scheduler.NativeBackgroundTask;
+import org.chromium.components.background_task_scheduler.BackgroundTask.TaskFinishedCallback;
+import org.chromium.components.background_task_scheduler.BackgroundTaskSchedulerPrefs;
+import org.chromium.components.background_task_scheduler.TaskIds;
+import org.chromium.components.background_task_scheduler.TaskParameters;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Handles servicing of Background Sync background tasks coming via
+ * background_task_scheduler component.
+ */
+public class BackgroundSyncBackgroundTask extends NativeBackgroundTask {
+    @Override
+    public @StartBeforeNativeResult int onStartTaskBeforeNativeLoaded(
+            Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
+        assert taskParameters.getTaskId() == TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID;
+
+        return StartBeforeNativeResult.LOAD_NATIVE;
+    }
+
+    @Override
+    protected void onStartTaskWithNative(
+            Context context, TaskParameters taskParameters, TaskFinishedCallback callback) {
+        // Record the delay from soonest expected wakeup time.
+        long delayFromExpectedMs = System.currentTimeMillis()
+                - taskParameters.getExtras().getLong(
+                        BackgroundSyncBackgroundTaskScheduler.SOONEST_EXPECTED_WAKETIME);
+        RecordHistogram.recordLongTimesHistogram(
+                "BackgroundSync.Wakeup.DelayTime", delayFromExpectedMs, TimeUnit.MILLISECONDS);
+
+        // Now that Chrome has been started, BackgroundSyncManager will
+        // eventually be created, and it'll fire any ready sync events.
+        // It'll also schedule a background task with the required delay.
+        // In case Chrome gets closed before native code gets to run,
+        // schedule a task to wake up Chrome with a delay, as a backup. This is
+        // done only if there isn't already a similar task scheduled. This'll
+        // be overwritten by a similar call from BackgroundSyncManager.
+        if (!BackgroundTaskSchedulerPrefs.getScheduledTasks().contains(
+                    BackgroundSyncBackgroundTask.class.getName())) {
+            BackgroundSyncBackgroundTaskScheduler.getInstance().scheduleOneShotTask();
+        }
+        callback.taskFinished(true);
+    }
+
+    @Override
+    protected boolean onStopTaskBeforeNativeLoaded(Context context, TaskParameters taskParameters) {
+        assert taskParameters.getTaskId() == TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID;
+
+        // Native didn't complete loading, but it was supposed to.
+        // Presume we need to reschedule.
+        return true;
+    }
+
+    @Override
+    protected boolean onStopTaskWithNative(Context context, TaskParameters taskParameters) {
+        assert taskParameters.getTaskId() == TaskIds.OFFLINE_PAGES_BACKGROUND_JOB_ID;
+
+        // Don't reschedule again.
+        return false;
+    }
+
+    @Override
+    public void reschedule(Context context) {
+        BackgroundSyncBackgroundTaskScheduler.getInstance().reschedule();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java
new file mode 100644
index 0000000..75d2b4a
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java
@@ -0,0 +1,111 @@
+// Copyright 2019 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.background_sync;
+
+import android.os.Bundle;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.base.annotations.CalledByNative;
+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 java.util.concurrent.TimeUnit;
+
+/**
+ * The {@link BackgroundSyncBackgroundTaskScheduler} singleton is responsible
+ * for scheduling and cancelling background tasks to wake Chrome up so that
+ * Background Sync events ready to be fired can be fired.
+ *
+ * Thread model: This class is to be run on the UI thread only.
+ */
+public class BackgroundSyncBackgroundTaskScheduler {
+    // Keep in sync with the default min_sync_recovery_time of
+    // BackgroundSyncParameters.
+    private static final long MIN_SYNC_RECOVERY_TIME = TimeUnit.MINUTES.toMillis(6);
+
+    // Bundle key for the timestamp of the soonest wakeup time expected for
+    // this task.
+    public static final String SOONEST_EXPECTED_WAKETIME = "SoonestWakeupTime";
+
+    private static class LazyHolder {
+        static final BackgroundSyncBackgroundTaskScheduler INSTANCE =
+                new BackgroundSyncBackgroundTaskScheduler();
+    }
+
+    @CalledByNative
+    public static BackgroundSyncBackgroundTaskScheduler getInstance() {
+        return LazyHolder.INSTANCE;
+    }
+
+    /**
+     * Cancels a task with Id BACKGROUND_SYNC_ONE_SHOT_JOB_ID, if there's one
+     * scheduled.
+     */
+    @VisibleForTesting
+    protected void cancelOneShotTask() {
+        BackgroundTaskSchedulerFactory.getScheduler().cancel(
+                ContextUtils.getApplicationContext(), TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID);
+    }
+
+    /**
+     * Schedules a one-off bakground task to wake the browser up on network
+     * connectivity. There is a delay of of six minutes before waking the
+     * browser.
+     */
+    protected boolean scheduleOneShotTask() {
+        return scheduleOneShotTask(MIN_SYNC_RECOVERY_TIME);
+    }
+
+    /**
+     * Schedules a one-off background task to wake the browser up on network
+     * connectivity and call into native code to fire ready Background Sync
+     * events.
+     * @param minDelayMs The minimum time to wait before waking the browser.
+     */
+    protected boolean scheduleOneShotTask(long minDelayMs) {
+        // Pack SOONEST_EXPECTED_WAKETIME in extras.
+        Bundle taskExtras = new Bundle();
+        taskExtras.putLong(SOONEST_EXPECTED_WAKETIME, System.currentTimeMillis() + minDelayMs);
+
+        TaskInfo taskInfo = TaskInfo.createOneOffTask(TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID,
+                                            BackgroundSyncBackgroundTask.class, minDelayMs)
+                                    .setRequiredNetworkType(TaskInfo.NetworkType.ANY)
+                                    .setUpdateCurrent(true)
+                                    .setIsPersisted(true)
+                                    .setExtras(taskExtras)
+                                    .build();
+        // This will overwrite any existing task with this ID.
+        return BackgroundTaskSchedulerFactory.getScheduler().schedule(
+                ContextUtils.getApplicationContext(), taskInfo);
+    }
+
+    /**
+     * Based on shouldLaunch, either creates or cancels a one-off background task
+     * to wake up Chrome upon network connectivity.
+     * @param shouldLaunch Whether to launch the browser in the background.
+     * @param minDelayMs The minimum time to wait before waking the browser.
+     */
+    @VisibleForTesting
+    @CalledByNative
+    protected void launchBrowserIfStopped(boolean shouldLaunch, long minDelayMs) {
+        if (!shouldLaunch) {
+            cancelOneShotTask();
+            return;
+        }
+
+        scheduleOneShotTask(minDelayMs);
+    }
+
+    /**
+     * Method for rescheduling a background task to wake up Chrome for processing
+     * one-shot Background Sync events in the event of an OS upgrade or
+     * Google Play Services upgrade.
+     */
+    public void reschedule() {
+        scheduleOneShotTask(MIN_SYNC_RECOVERY_TIME);
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/background_sync/OWNERS b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/OWNERS
new file mode 100644
index 0000000..59f4eacf
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/background_sync/OWNERS
@@ -0,0 +1,4 @@
+file://chrome/browser/background_sync/OWNERS
+
+# COMPONENT: Blink>BackgroundSync
+# TEAM: [email protected]
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index b7ed9bbd..4530475 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -171,6 +171,8 @@
   "java/src/org/chromium/chrome/browser/autofill_assistant/payment/AutofillAssistantPaymentRequest.java",
   "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestBottomBar.java",
   "java/src/org/chromium/chrome/browser/autofill_assistant/payment/PaymentRequestUI.java",
+  "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
+  "java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTask.java",
   "java/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTask.java",
   "java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
   "java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
@@ -2395,6 +2397,7 @@
   "junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/KeyboardAccessoryTabLayoutControllerTest.java",
   "junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/ManualFillingControllerTest.java",
   "junit/src/org/chromium/chrome/browser/autofill/keyboard_accessory/PasswordAccessorySheetControllerTest.java",
+  "junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskSchedulerTest.java",
   "junit/src/org/chromium/chrome/browser/background_task_scheduler/NativeBackgroundTaskTest.java",
   "junit/src/org/chromium/chrome/browser/browseractions/BrowserActionsIntentTest.java",
   "junit/src/org/chromium/chrome/browser/browserservices/ClearDataDialogResultRecorderTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherTest.java
index ba68340..47f4b599 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/BackgroundSyncLauncherTest.java
@@ -53,11 +53,10 @@
         // Use a semaphore to wait for the callback to be called.
         final Semaphore semaphore = new Semaphore(0);
 
-        BackgroundSyncLauncher.ShouldLaunchCallback callback =
-                shouldLaunch -> {
-                    mShouldLaunchResult = shouldLaunch;
-                    semaphore.release();
-                };
+        BackgroundSyncLauncher.ShouldLaunchCallback callback = shouldLaunch -> {
+            mShouldLaunchResult = shouldLaunch;
+            semaphore.release();
+        };
 
         BackgroundSyncLauncher.shouldLaunchBrowserIfStopped(callback);
         // Wait on the callback to be called.
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
index c412172..c7e9d78 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ChromeBackgroundServiceTest.java
@@ -23,6 +23,8 @@
 import org.chromium.chrome.browser.ntp.snippets.SnippetsLauncher;
 import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
 
+import java.util.HashMap;
+
 /**
  * Tests {@link ChromeBackgroundService}.
  */
@@ -78,9 +80,13 @@
 
     @Before
     public void setUp() throws Exception {
+        HashMap<String, Boolean> features = new HashMap<String, Boolean>();
+        features.put(ChromeFeatureList.BACKGROUND_TASK_SCHEDULER_FOR_BACKGROUND_SYNC, false);
+        ChromeFeatureList.setTestFeatures(features);
         BackgroundSyncLauncher.setGCMEnabled(false);
-        RecordHistogram.setDisabledForTests(true);
         mSyncLauncher = BackgroundSyncLauncher.create();
+
+        RecordHistogram.setDisabledForTests(true);
         mSnippetsLauncher = SnippetsLauncher.create();
         mTaskService = new MockTaskService();
     }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskSchedulerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskSchedulerTest.java
new file mode 100644
index 0000000..c0804bd
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskSchedulerTest.java
@@ -0,0 +1,151 @@
+// Copyright 2019 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.background_sync;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import org.junit.Before;
+import org.junit.Rule;
+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.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.browser.ChromeFeatureList;
+import org.chromium.chrome.test.support.DisableHistogramsRule;
+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 java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/** Unit tests for BackgroundSyncBackgroundTaskScheduler. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class BackgroundSyncBackgroundTaskSchedulerTest {
+    @Rule
+    public DisableHistogramsRule mDisableHistogramsRule = new DisableHistogramsRule();
+
+    @Mock
+    private BackgroundTaskScheduler mTaskScheduler;
+    @Captor
+    ArgumentCaptor<TaskInfo> mTaskInfo;
+
+    private static final long ONE_DAY_IN_MILLISECONDS = TimeUnit.DAYS.toMillis(1);
+    private static final long ONE_WEEK_IN_MILLISECONDS = TimeUnit.DAYS.toMillis(7);
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        BackgroundTaskSchedulerFactory.setSchedulerForTesting(mTaskScheduler);
+        HashMap<String, Boolean> features = new HashMap<String, Boolean>();
+        features.put(ChromeFeatureList.BACKGROUND_TASK_SCHEDULER_FOR_BACKGROUND_SYNC, true);
+        ChromeFeatureList.setTestFeatures(features);
+        doReturn(true)
+                .when(mTaskScheduler)
+                .schedule(eq(RuntimeEnvironment.application), mTaskInfo.capture());
+    }
+
+    private void verifyFixedTaskInfoValues(TaskInfo info) {
+        assertEquals(TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID, info.getTaskId());
+        assertEquals(BackgroundSyncBackgroundTask.class, info.getBackgroundTaskClass());
+        assertTrue(info.isPersisted());
+        assertFalse(info.isPeriodic());
+        assertEquals(TaskInfo.NetworkType.ANY, info.getRequiredNetworkType());
+
+        long expectedSoonestDelayTime = info.getExtras().getLong(
+                BackgroundSyncBackgroundTaskScheduler.SOONEST_EXPECTED_WAKETIME);
+        assertTrue(expectedSoonestDelayTime > 0L);
+    }
+
+    @Test
+    @Feature({"BackgroundSync"})
+    public void testLaunchBrowserIfStopped() {
+        BackgroundSyncBackgroundTaskScheduler.getInstance().launchBrowserIfStopped(
+                /* shouldLaunch= */ true,
+                /* minDelayMs= */ ONE_DAY_IN_MILLISECONDS);
+        verify(mTaskScheduler, times(1))
+                .schedule(eq(RuntimeEnvironment.application), eq(mTaskInfo.getValue()));
+
+        TaskInfo taskInfo = mTaskInfo.getValue();
+        verifyFixedTaskInfoValues(taskInfo);
+
+        assertEquals(ONE_DAY_IN_MILLISECONDS, taskInfo.getOneOffInfo().getWindowEndTimeMs());
+    }
+
+    @Test
+    @Feature({"BackgroundSync"})
+    public void testCancelOneShotTask() {
+        BackgroundSyncBackgroundTaskScheduler.getInstance().launchBrowserIfStopped(
+                /* shouldLaunch= */ true,
+                /* minDelayMs= */ ONE_DAY_IN_MILLISECONDS);
+        verify(mTaskScheduler, times(1))
+                .schedule(eq(RuntimeEnvironment.application), eq(mTaskInfo.getValue()));
+
+        doNothing()
+                .when(mTaskScheduler)
+                .cancel(eq(RuntimeEnvironment.application),
+                        eq(TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID));
+
+        BackgroundSyncBackgroundTaskScheduler.getInstance().cancelOneShotTask();
+        verify(mTaskScheduler, times(1))
+                .cancel(eq(RuntimeEnvironment.application),
+                        eq(TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID));
+    }
+
+    @Test
+    @Feature({"BackgroundSync"})
+    public void testLaunchBrowserCalledTwice() {
+        BackgroundSyncBackgroundTaskScheduler.getInstance().launchBrowserIfStopped(
+                /* shouldLaunch= */ true,
+                /* minDelayMs= */ ONE_DAY_IN_MILLISECONDS);
+        verify(mTaskScheduler, times(1))
+                .schedule(eq(RuntimeEnvironment.application), eq(mTaskInfo.getValue()));
+
+        TaskInfo taskInfo = mTaskInfo.getValue();
+        assertEquals(ONE_DAY_IN_MILLISECONDS, taskInfo.getOneOffInfo().getWindowEndTimeMs());
+
+        BackgroundSyncBackgroundTaskScheduler.getInstance().launchBrowserIfStopped(
+                /* shouldLaunch= */ true,
+                /* minDelayMs= */ ONE_WEEK_IN_MILLISECONDS);
+        verify(mTaskScheduler, times(1))
+                .schedule(eq(RuntimeEnvironment.application), eq(mTaskInfo.getValue()));
+
+        taskInfo = mTaskInfo.getValue();
+        assertEquals(ONE_WEEK_IN_MILLISECONDS, taskInfo.getOneOffInfo().getWindowEndTimeMs());
+    }
+
+    @Test
+    @Feature({"BackgroundSync"})
+    public void testLaunchBrowserThenCancel() {
+        BackgroundSyncBackgroundTaskScheduler.getInstance().launchBrowserIfStopped(
+                /* shouldLaunch= */ true,
+                /* minDelayMs= */ ONE_DAY_IN_MILLISECONDS);
+        BackgroundSyncBackgroundTaskScheduler.getInstance().launchBrowserIfStopped(
+                /* shouldLaunch= */ false,
+                /* minDelayMs= */ ONE_DAY_IN_MILLISECONDS);
+
+        verify(mTaskScheduler, times(1))
+                .schedule(eq(RuntimeEnvironment.application), eq(mTaskInfo.getValue()));
+        verify(mTaskScheduler, times(1))
+                .cancel(eq(RuntimeEnvironment.application),
+                        eq(TaskIds.BACKGROUND_SYNC_ONE_SHOT_JOB_ID));
+    }
+}
\ No newline at end of file
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index bffa23b..471e41ce 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4877,6 +4877,7 @@
       "../android/java/src/org/chromium/chrome/browser/autofill_assistant/overlay/AssistantOverlayModel.java",
       "../android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestDelegate.java",
       "../android/java/src/org/chromium/chrome/browser/autofill_assistant/payment/AssistantPaymentRequestModel.java",
+      "../android/java/src/org/chromium/chrome/browser/background_sync/BackgroundSyncBackgroundTaskScheduler.java",
       "../android/java/src/org/chromium/chrome/browser/banners/AppBannerManager.java",
       "../android/java/src/org/chromium/chrome/browser/banners/AppBannerUiDelegateAndroid.java",
       "../android/java/src/org/chromium/chrome/browser/bookmarks/BookmarkBridge.java",
diff --git a/chrome/browser/android/background_sync_launcher_android.cc b/chrome/browser/android/background_sync_launcher_android.cc
index e5f58674..7b182c6 100644
--- a/chrome/browser/android/background_sync_launcher_android.cc
+++ b/chrome/browser/android/background_sync_launcher_android.cc
@@ -4,7 +4,10 @@
 
 #include "chrome/browser/android/background_sync_launcher_android.h"
 
+#include "base/feature_list.h"
+#include "chrome/browser/android/chrome_feature_list.h"
 #include "content/public/browser/browser_thread.h"
+#include "jni/BackgroundSyncBackgroundTaskScheduler_jni.h"
 #include "jni/BackgroundSyncLauncher_jni.h"
 
 using content::BrowserThread;
@@ -42,8 +45,18 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   JNIEnv* env = base::android::AttachCurrentThread();
-  Java_BackgroundSyncLauncher_launchBrowserIfStopped(
-      env, java_launcher_, launch_when_next_online, min_delay_ms);
+
+  if (!base::FeatureList::IsEnabled(
+          chrome::android::kBackgroundTaskSchedulerForBackgroundSync)) {
+    Java_BackgroundSyncLauncher_launchBrowserIfStopped(
+        env, java_gcm_network_manager_launcher_, launch_when_next_online,
+        min_delay_ms);
+    return;
+  }
+
+  Java_BackgroundSyncBackgroundTaskScheduler_launchBrowserIfStopped(
+      env, java_background_sync_background_task_scheduler_launcher_,
+      launch_when_next_online, min_delay_ms);
 }
 
 // static
@@ -67,12 +80,26 @@
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
   JNIEnv* env = base::android::AttachCurrentThread();
-  java_launcher_.Reset(Java_BackgroundSyncLauncher_create(env));
+
+  if (!base::FeatureList::IsEnabled(
+          chrome::android::kBackgroundTaskSchedulerForBackgroundSync)) {
+    java_gcm_network_manager_launcher_.Reset(
+        Java_BackgroundSyncLauncher_create(env));
+    return;
+  }
+
+  java_background_sync_background_task_scheduler_launcher_.Reset(
+      Java_BackgroundSyncBackgroundTaskScheduler_getInstance(env));
 }
 
 BackgroundSyncLauncherAndroid::~BackgroundSyncLauncherAndroid() {
   DCHECK_CURRENTLY_ON(BrowserThread::UI);
 
+  if (base::FeatureList::IsEnabled(
+          chrome::android::kBackgroundTaskSchedulerForBackgroundSync)) {
+    return;
+  }
+
   JNIEnv* env = base::android::AttachCurrentThread();
-  Java_BackgroundSyncLauncher_destroy(env, java_launcher_);
+  Java_BackgroundSyncLauncher_destroy(env, java_gcm_network_manager_launcher_);
 }
diff --git a/chrome/browser/android/background_sync_launcher_android.h b/chrome/browser/android/background_sync_launcher_android.h
index be547a8..0c93da7 100644
--- a/chrome/browser/android/background_sync_launcher_android.h
+++ b/chrome/browser/android/background_sync_launcher_android.h
@@ -40,7 +40,10 @@
   void LaunchBrowserIfStoppedImpl(bool launch_when_next_online,
                                   int64_t min_delay_ms);
 
-  base::android::ScopedJavaGlobalRef<jobject> java_launcher_;
+  base::android::ScopedJavaGlobalRef<jobject>
+      java_gcm_network_manager_launcher_;
+  base::android::ScopedJavaGlobalRef<jobject>
+      java_background_sync_background_task_scheduler_launcher_;
   DISALLOW_COPY_AND_ASSIGN(BackgroundSyncLauncherAndroid);
 };
 
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index 722011e6..ed6aaea 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -92,6 +92,7 @@
     &kAndroidPayIntegrationV2,
     &kAndroidPaymentApps,
     &kAndroidSiteSettingsUIRefresh,
+    &kBackgroundTaskSchedulerForBackgroundSync,
     &kCastDeviceFilter,
     &kCCTBackgroundTab,
     &kCCTExternalLinkHandling,
@@ -229,6 +230,10 @@
 const base::Feature kBackgroundTaskComponentUpdate{
     "BackgroundTaskComponentUpdate", base::FEATURE_DISABLED_BY_DEFAULT};
 
+const base::Feature kBackgroundTaskSchedulerForBackgroundSync{
+    "BackgroundTaskSchedulerForBackgroundSync",
+    base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Used in downstream code.
 const base::Feature kCastDeviceFilter{"CastDeviceFilter",
                                       base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index c7997f1..e527f3d 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -20,6 +20,7 @@
 extern const base::Feature kAndroidPaymentApps;
 extern const base::Feature kAndroidSiteSettingsUIRefresh;
 extern const base::Feature kBackgroundTaskComponentUpdate;
+extern const base::Feature kBackgroundTaskSchedulerForBackgroundSync;
 extern const base::Feature kCastDeviceFilter;
 extern const base::Feature kCCTBackgroundTab;
 extern const base::Feature kCCTExternalLinkHandling;