Add a GreedyScheduler.

GreedyScheduler optimistically starts executing unconstrained
non-timed work in the Processor.  This is the replacement for
ForegroundProcessor that starts work without incurring an IPC
delay.

For this change to take effect, there is now a list of
Schedulers maintained by WorkManagerImpl.

Cancelled work id's are now maintained by the Processor
because there is more than one Scheduler available at a time.
The Scheduler interface no longer refers to cancelled id's.

SystemJobService and FirebaseJobService no longer need to
know about their Schedulers.

Test: Updated and ran tests.

Change-Id: Ie9e67bea0e2b12e6077a8230695374f45a156904
diff --git a/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java b/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java
index acf28fd..1697b19 100644
--- a/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java
+++ b/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java
@@ -15,6 +15,7 @@
  */
 package android.arch.background.workmanager.impl.background.firebase;
 
+import android.arch.background.workmanager.impl.Scheduler;
 import android.arch.background.workmanager.impl.WorkDatabase;
 import android.arch.background.workmanager.impl.WorkManagerImpl;
 import android.arch.background.workmanager.impl.logger.Logger;
@@ -36,19 +37,18 @@
 
     @Override
     public void onReceive(final Context context, Intent intent) {
-        // TODO(xbhatnag): Avoid using getWorkDatabase() from WorkManager
         final PendingResult pendingResult = goAsync();
         final String workSpecId = intent.getStringExtra(WORKSPEC_ID_KEY);
         final WorkManagerImpl workManagerImpl = WorkManagerImpl.getInstance();
-        final FirebaseJobScheduler scheduler =
-                (FirebaseJobScheduler) workManagerImpl.getBackgroundScheduler();
         final WorkDatabase database = workManagerImpl.getWorkDatabase();
         new Thread(new Runnable() {
             @Override
             public void run() {
                 WorkSpec workSpec = database.workSpecDao().getWorkSpec(workSpecId);
                 if (workSpec != null) {
-                    scheduler.scheduleNow(workSpec);
+                    for (Scheduler scheduler : workManagerImpl.getSchedulers()) {
+                        scheduler.schedule(workSpec);
+                    }
                 } else {
                     Logger.error(TAG, "WorkSpec not found! Cannot schedule!");
                 }
diff --git a/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobScheduler.java b/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobScheduler.java
index 1726a5b..cf10b99 100644
--- a/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobScheduler.java
+++ b/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobScheduler.java
@@ -34,9 +34,6 @@
 import com.google.android.gms.common.ConnectionResult;
 import com.google.android.gms.common.GoogleApiAvailability;
 
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * A class that schedules work using {@link FirebaseJobDispatcher}.
  *
@@ -52,7 +49,6 @@
     private FirebaseJobConverter mJobConverter;
     private IdGenerator mIdGenerator;
     private AlarmManager mAlarmManager;
-    private Set<String> mCancelledIds;
 
     public FirebaseJobScheduler(Context context) {
         mAppContext = context.getApplicationContext();
@@ -63,7 +59,6 @@
         }
         mDispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(mAppContext));
         mJobConverter = new FirebaseJobConverter(mDispatcher);
-        mCancelledIds = new HashSet<>();
     }
 
     @Override
@@ -79,15 +74,9 @@
 
     @Override
     public void cancel(@NonNull String workSpecId) {
-        mCancelledIds.add(workSpecId);
         mDispatcher.cancel(workSpecId);
     }
 
-    @Override
-    public boolean isCancelled(@NonNull String workSpecId) {
-        return mCancelledIds.contains(workSpecId);
-    }
-
     void scheduleNow(WorkSpec workSpec) {
         Job job = mJobConverter.convert(workSpec);
         Logger.debug(TAG, "Scheduling work now, ID: %s", workSpec.getId());
diff --git a/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobService.java b/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobService.java
index 2ede916..d9d1b79 100644
--- a/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobService.java
+++ b/background/workmanager-firebase/src/main/java/android/arch/background/workmanager/impl/background/firebase/FirebaseJobService.java
@@ -17,7 +17,6 @@
 package android.arch.background.workmanager.impl.background.firebase;
 
 import android.arch.background.workmanager.impl.ExecutionListener;
-import android.arch.background.workmanager.impl.Scheduler;
 import android.arch.background.workmanager.impl.WorkManagerImpl;
 import android.arch.background.workmanager.impl.logger.Logger;
 import android.support.annotation.NonNull;
@@ -39,7 +38,6 @@
 public class FirebaseJobService extends JobService implements ExecutionListener {
     private static final String TAG = "FirebaseJobService";
     private WorkManagerImpl mWorkManagerImpl;
-    private Scheduler mScheduler;
     private final Map<String, JobParameters> mJobParameters = new HashMap<>();
 
     @Override
@@ -47,7 +45,6 @@
         super.onCreate();
         mWorkManagerImpl = WorkManagerImpl.getInstance();
         mWorkManagerImpl.getProcessor().addExecutionListener(this);
-        mScheduler = mWorkManagerImpl.getBackgroundScheduler();
     }
 
     @Override
@@ -86,7 +83,7 @@
             mJobParameters.remove(workSpecId);
         }
         mWorkManagerImpl.stopWork(workSpecId);
-        return !mScheduler.isCancelled(workSpecId);
+        return !mWorkManagerImpl.getProcessor().isCancelled(workSpecId);
     }
 
     @Override
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/executors/SynchronousExecutorService.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/executors/SynchronousExecutorService.java
deleted file mode 100644
index 9395f12..0000000
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/executors/SynchronousExecutorService.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.arch.background.workmanager.executors;
-
-import android.support.annotation.NonNull;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.Delayed;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * A synchronous {@link ScheduledExecutorService}.
- */
-public class SynchronousExecutorService implements ScheduledExecutorService {
-
-    private boolean mShutdown;
-
-    @NonNull
-    @Override
-    public ScheduledFuture<?> schedule(
-            @NonNull Runnable command, long delay, @NonNull TimeUnit unit) {
-        return doBlocking(command, delay, unit);
-    }
-
-    @NonNull
-    @Override
-    public <V> ScheduledFuture<V> schedule(
-            @NonNull Callable<V> callable, long delay, @NonNull TimeUnit unit) {
-        try {
-            return doBlocking(callable, delay, unit);
-        } catch (Exception e) {
-            return new SynchronizedScheduledFuture<>();
-        }
-    }
-
-    @NonNull
-    @Override
-    public ScheduledFuture<?> scheduleAtFixedRate(
-            @NonNull Runnable command, long initialDelay, long period, @NonNull TimeUnit unit) {
-        throw new UnsupportedOperationException();
-    }
-
-    @NonNull
-    @Override
-    public ScheduledFuture<?> scheduleWithFixedDelay(
-            @NonNull Runnable command, long initialDelay, long delay, @NonNull TimeUnit unit) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void shutdown() {
-        mShutdown = true;
-    }
-
-    @NonNull
-    @Override
-    public List<Runnable> shutdownNow() {
-        mShutdown = true;
-        return new ArrayList<>(0);
-    }
-
-    @Override
-    public boolean isShutdown() {
-        return mShutdown;
-    }
-
-    @Override
-    public boolean isTerminated() {
-        return mShutdown;   // Assume all tasks have completed after shutdown.
-    }
-
-    @Override
-    public boolean awaitTermination(
-            long timeout, @NonNull TimeUnit unit) throws InterruptedException {
-        return false;
-    }
-
-    @NonNull
-    @Override
-    public <T> Future<T> submit(@NonNull Callable<T> task) {
-        try {
-            return doBlocking(task, 0, TimeUnit.MILLISECONDS);
-        } catch (Exception e) {
-            return new SynchronizedScheduledFuture<>();
-        }
-    }
-
-    @NonNull
-    @Override
-    public <T> Future<T> submit(@NonNull Runnable task, T result) {
-        return doBlocking(task, 0, TimeUnit.MILLISECONDS);
-    }
-
-    @NonNull
-    @Override
-    public Future<?> submit(@NonNull Runnable task) {
-        return doBlocking(task, 0, TimeUnit.MILLISECONDS);
-    }
-
-    @NonNull
-    @Override
-    public <T> List<Future<T>> invokeAll(@NonNull Collection<? extends Callable<T>> tasks)
-            throws InterruptedException {
-        throw new UnsupportedOperationException();
-    }
-
-    @NonNull
-    @Override
-    public <T> List<Future<T>> invokeAll(
-            @NonNull Collection<? extends Callable<T>> tasks, long timeout, @NonNull TimeUnit unit)
-            throws InterruptedException {
-        throw new UnsupportedOperationException();
-    }
-
-    @NonNull
-    @Override
-    public <T> T invokeAny(@NonNull Collection<? extends Callable<T>> tasks)
-            throws InterruptedException, ExecutionException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public <T> T invokeAny(
-            @NonNull Collection<? extends Callable<T>> tasks, long timeout, @NonNull TimeUnit unit)
-            throws InterruptedException, ExecutionException, TimeoutException {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void execute(@NonNull Runnable command) {
-        doBlocking(command, 0, TimeUnit.MILLISECONDS);
-    }
-
-    private <T> ScheduledFuture<T> doBlocking(Runnable command, long delay, TimeUnit unit) {
-        try {
-            long millis = unit.toMillis(delay);
-            if (millis > 0L) {
-                Thread.sleep(millis);
-            }
-        } catch (InterruptedException e) {
-            // Do nothing.
-        }
-        command.run();
-        return new SynchronizedScheduledFuture<>();
-    }
-
-    private <T> ScheduledFuture<T> doBlocking(Callable<T> command, long delay, TimeUnit unit)
-            throws Exception {
-        try {
-            long millis = unit.toMillis(delay);
-            if (millis > 0L) {
-                Thread.sleep(millis);
-            }
-        } catch (InterruptedException e) {
-            // Do nothing.
-        }
-        command.call();
-        return new SynchronizedScheduledFuture<>();
-    }
-
-    private static class SynchronizedScheduledFuture<V> implements ScheduledFuture<V> {
-
-        @Override
-        public long getDelay(@NonNull TimeUnit unit) {
-            return 0;
-        }
-
-        @Override
-        public int compareTo(@NonNull Delayed o) {
-            return 0;
-        }
-
-        @Override
-        public boolean cancel(boolean mayInterruptIfRunning) {
-            return false;
-        }
-
-        @Override
-        public boolean isCancelled() {
-            return false;
-        }
-
-        @Override
-        public boolean isDone() {
-            return true;
-        }
-
-        @Override
-        public V get() throws InterruptedException, ExecutionException {
-            return null;
-        }
-
-        @Override
-        public V get(long timeout, @NonNull TimeUnit unit)
-                throws InterruptedException, ExecutionException, TimeoutException {
-            return null;
-        }
-    }
-}
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/ProcessorTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/ProcessorTest.java
index 04bfcba3..7c355a0 100644
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/ProcessorTest.java
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/ProcessorTest.java
@@ -32,6 +32,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Collections;
 import java.util.concurrent.Executors;
 
 @RunWith(AndroidJUnit4.class)
@@ -44,7 +45,7 @@
         mProcessor = new Processor(
                 appContext,
                 mDatabase,
-                mock(Scheduler.class),
+                Collections.singletonList(mock(Scheduler.class)),
                 Executors.newSingleThreadScheduledExecutor()) {
         };
     }
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkContinuationImplTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkContinuationImplTest.java
index de23388..f99ceb5 100644
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkContinuationImplTest.java
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkContinuationImplTest.java
@@ -28,7 +28,6 @@
 import android.arch.background.workmanager.TestLifecycleOwner;
 import android.arch.background.workmanager.Work;
 import android.arch.background.workmanager.WorkContinuation;
-import android.arch.background.workmanager.executors.SynchronousExecutorService;
 import android.arch.background.workmanager.impl.utils.taskexecutor.InstantTaskExecutorRule;
 import android.arch.background.workmanager.worker.TestWorker;
 import android.arch.core.executor.ArchTaskExecutor;
@@ -47,6 +46,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executors;
 
 
 @RunWith(AndroidJUnit4.class)
@@ -85,7 +85,7 @@
         WorkManagerConfiguration configuration = new WorkManagerConfiguration(
                 context,
                 true,
-                new SynchronousExecutorService());
+                Executors.newSingleThreadExecutor());
         mWorkManagerImpl = new WorkManagerImpl(context, configuration);
         mDatabase = mWorkManagerImpl.getWorkDatabase();
     }
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkManagerImplTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkManagerImplTest.java
index 7c5c8c6..59016f6 100644
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkManagerImplTest.java
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkManagerImplTest.java
@@ -53,7 +53,6 @@
 import android.arch.background.workmanager.WorkContinuation;
 import android.arch.background.workmanager.WorkManagerTest;
 import android.arch.background.workmanager.WorkStatus;
-import android.arch.background.workmanager.executors.SynchronousExecutorService;
 import android.arch.background.workmanager.impl.model.Dependency;
 import android.arch.background.workmanager.impl.model.DependencyDao;
 import android.arch.background.workmanager.impl.model.WorkSpec;
@@ -85,6 +84,7 @@
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.Executors;
 
 @RunWith(AndroidJUnit4.class)
 public class WorkManagerImplTest extends WorkManagerTest {
@@ -117,7 +117,7 @@
         WorkManagerConfiguration configuration = new WorkManagerConfiguration(
                 context,
                 true,
-                new SynchronousExecutorService());
+                Executors.newSingleThreadExecutor());
         mWorkManagerImpl = new WorkManagerImpl(context, configuration);
         mDatabase = mWorkManagerImpl.getWorkDatabase();
     }
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkerWrapperTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkerWrapperTest.java
index b9f375f..666be5b 100644
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkerWrapperTest.java
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/WorkerWrapperTest.java
@@ -63,6 +63,7 @@
 import org.mockito.ArgumentCaptor;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executors;
 
@@ -240,7 +241,7 @@
 
         new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getId())
                 .withListener(mMockListener)
-                .withScheduler(mMockScheduler)
+                .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build()
                 .run();
 
@@ -344,7 +345,7 @@
 
         new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getId())
                 .withListener(mMockListener)
-                .withScheduler(mMockScheduler)
+                .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build()
                 .run();
 
@@ -469,7 +470,7 @@
         Scheduler mockScheduler = mock(Scheduler.class);
 
         new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
-                .withScheduler(mockScheduler)
+                .withSchedulers(Collections.singletonList(mockScheduler))
                 .build()
                 .run();
 
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 6c51ed8..b9b2867 100644
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -55,6 +55,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -103,7 +104,7 @@
         mProcessor = new Processor(
                 mContext,
                 mDatabase,
-                mScheduler,
+                Collections.singletonList(mScheduler),
                 // simulate real world use-case
                 Executors.newSingleThreadExecutor());
         mSpyProcessor = spy(mProcessor);
diff --git a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/utils/EnqueueRunnableTest.java b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/utils/EnqueueRunnableTest.java
index 0789e14..8a2f722 100644
--- a/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/utils/EnqueueRunnableTest.java
+++ b/background/workmanager/src/androidTest/java/android/arch/background/workmanager/impl/utils/EnqueueRunnableTest.java
@@ -45,6 +45,7 @@
     public void testScheduleWorkInBackground_isCalled() {
         EnqueueRunnable runnable = spy(new EnqueueRunnable(mWorkContinuation));
         doNothing().when(runnable).addToDatabase();
+        doNothing().when(runnable).scheduleWorkInBackground();
         runnable.run();
         verify(runnable, times(1)).scheduleWorkInBackground();
     }
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Processor.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Processor.java
index b9d14f1c..c86d8e1 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Processor.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Processor.java
@@ -22,8 +22,10 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
@@ -35,25 +37,29 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class Processor implements ExecutionListener {
     private static final String TAG = "Processor";
-    protected Context mAppContext;
-    protected WorkDatabase mWorkDatabase;
 
-    protected Map<String, Future<?>> mEnqueuedWorkMap;
-    protected Scheduler mScheduler;
-    protected ExecutorService mExecutorService;
+    private Context mAppContext;
+    private WorkDatabase mWorkDatabase;
+
+    private Map<String, Future<?>> mEnqueuedWorkMap;
+    private List<Scheduler> mSchedulers;
+    private ExecutorService mExecutorService;
+
+    private Set<String> mCancelledIds;
 
     private final List<ExecutionListener> mOuterListeners;
 
     public Processor(
             Context appContext,
             WorkDatabase workDatabase,
-            Scheduler scheduler,
+            List<Scheduler> schedulers,
             ExecutorService executorService) {
         mAppContext = appContext;
         mWorkDatabase = workDatabase;
         mEnqueuedWorkMap = new HashMap<>();
-        mScheduler = scheduler;
+        mSchedulers = schedulers;
         mExecutorService = executorService;
+        mCancelledIds = new HashSet<>();
         mOuterListeners = new ArrayList<>();
     }
 
@@ -73,7 +79,7 @@
 
         WorkerWrapper workWrapper = new WorkerWrapper.Builder(mAppContext, mWorkDatabase, id)
                 .withListener(this)
-                .withScheduler(mScheduler)
+                .withSchedulers(mSchedulers)
                 .build();
         mEnqueuedWorkMap.put(id, mExecutorService.submit(workWrapper));
         Logger.debug(TAG, "%s: processing %s", getClass().getSimpleName(), id);
@@ -83,10 +89,10 @@
     /**
      * Tries to stop a unit of work.
      *
-     * @param id The work id to stop.
+     * @param id The work id to stop
      * @param mayInterruptIfRunning If {@code true}, we try to interrupt the {@link Future} if it's
      *                              running
-     * @return {@code true} if the work was stopped successfully.
+     * @return {@code true} if the work was stopped successfully
      */
     public synchronized boolean stopWork(String id, boolean mayInterruptIfRunning) {
         Logger.debug(TAG,
@@ -112,6 +118,26 @@
     }
 
     /**
+     * Sets the given {@code id} as cancelled.  This does not actually stop any processing; call
+     * {@link #stopWork(String, boolean)} to do that.
+     *
+     * @param id  The work id to mark as cancelled
+     */
+    public synchronized void setCancelled(String id) {
+        mCancelledIds.add(id);
+    }
+
+    /**
+     * Determines if the given {@code id} is marked as cancelled.
+     *
+     * @param id The work id to query
+     * @return {@code true} if the id has already been marked as cancelled
+     */
+    public synchronized boolean isCancelled(String id) {
+        return mCancelledIds.contains(id);
+    }
+
+    /**
      * @return {@code true} if the processor has work to process.
      */
     public synchronized boolean hasWork() {
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Scheduler.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Scheduler.java
index ce4d181..45e98a6 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Scheduler.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/Scheduler.java
@@ -41,13 +41,4 @@
      * @param workSpecId The id of the work to stopWork
      */
     void cancel(@NonNull String workSpecId);
-
-    /**
-     * Determines if the given {@link WorkSpec} id has already been cancelled.
-     *
-     * @param workSpecId  The id of the work to check
-     * @return {@code true} if the work has been cancelled through {@link #cancel(String)};
-     *         {@code false} otherwise
-     */
-    boolean isCancelled(@NonNull String workSpecId);
 }
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkManagerImpl.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkManagerImpl.java
index 98570a7..6d8fe52 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkManagerImpl.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkManagerImpl.java
@@ -22,6 +22,7 @@
 import android.arch.background.workmanager.WorkContinuation;
 import android.arch.background.workmanager.WorkManager;
 import android.arch.background.workmanager.WorkStatus;
+import android.arch.background.workmanager.impl.background.greedy.GreedyScheduler;
 import android.arch.background.workmanager.impl.model.WorkSpec;
 import android.arch.background.workmanager.impl.model.WorkSpecDao;
 import android.arch.background.workmanager.impl.utils.CancelWorkRunnable;
@@ -57,10 +58,9 @@
     public static final int MAX_PRE_JOB_SCHEDULER_API_LEVEL = 23;
     public static final int MIN_JOB_SCHEDULER_API_LEVEL = 24;
 
-    private WorkManagerConfiguration mConfiguration;
     private WorkDatabase mWorkDatabase;
     private TaskExecutor mTaskExecutor;
-    private Scheduler mBackgroundScheduler;
+    private List<Scheduler> mSchedulers;
     private Processor mProcessor;
 
     private static WorkManagerImpl sInstance = null;
@@ -86,15 +86,17 @@
     }
 
     WorkManagerImpl(Context context, WorkManagerConfiguration configuration) {
-        // TODO(janclarin): Remove context parameter.
-        mConfiguration = configuration;
         mWorkDatabase = configuration.getWorkDatabase();
-        mBackgroundScheduler = configuration.getBackgroundScheduler();
+
+        mSchedulers = new ArrayList<>();
+        mSchedulers.add(configuration.getBackgroundScheduler());
+        mSchedulers.add(new GreedyScheduler(this));
+
         mTaskExecutor = WorkManagerTaskExecutor.getInstance();
         mProcessor = new Processor(
                 context.getApplicationContext(),
                 mWorkDatabase,
-                mBackgroundScheduler,
+                mSchedulers,
                 configuration.getExecutorService());
     }
 
@@ -108,13 +110,13 @@
     }
 
     /**
-     * @return The {@link Scheduler} associated with this WorkManager based on the device's
+     * @return The {@link Scheduler}s associated with this WorkManager based on the device's
      * capabilities, SDK version, etc.
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public @NonNull Scheduler getBackgroundScheduler() {
-        return mBackgroundScheduler;
+    public @NonNull List<Scheduler> getSchedulers() {
+        return mSchedulers;
     }
 
     /**
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkerWrapper.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkerWrapper.java
index 9aa9137..4301e70 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkerWrapper.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/WorkerWrapper.java
@@ -54,7 +54,7 @@
     private Context mAppContext;
     private String mWorkSpecId;
     private ExecutionListener mListener;
-    private Scheduler mScheduler;
+    private List<Scheduler> mSchedulers;
     private WorkSpec mWorkSpec;
     Worker mWorker;
 
@@ -66,7 +66,7 @@
         mAppContext = builder.mAppContext;
         mWorkSpecId = builder.mWorkSpecId;
         mListener = builder.mListener;
-        mScheduler = builder.mScheduler;
+        mSchedulers = builder.mSchedulers;
 
         mWorkDatabase = builder.mWorkDatabase;
         mWorkSpecDao = mWorkDatabase.workSpecDao();
@@ -278,12 +278,15 @@
                         "Setting status to enqueued for %s Works that were dependent on Work ID %s",
                         unblockedWorkCount, mWorkSpecId);
             }
-            mWorkDatabase.setTransactionSuccessful();
 
-            if (mScheduler != null) {
+            if (mSchedulers != null) {
                 WorkSpec[] unblockedWorkSpecs = mWorkSpecDao.getWorkSpecs(unblockedWorkIds);
-                mScheduler.schedule(unblockedWorkSpecs);
+                for (Scheduler scheduler : mSchedulers) {
+                    scheduler.schedule(unblockedWorkSpecs);
+                }
             }
+
+            mWorkDatabase.setTransactionSuccessful();
         } finally {
             mWorkDatabase.endTransaction();
             notifyListener(true, false);
@@ -318,7 +321,7 @@
         private WorkDatabase mWorkDatabase;
         private String mWorkSpecId;
         private ExecutionListener mListener;
-        private Scheduler mScheduler;
+        private List<Scheduler> mSchedulers;
 
         Builder(@NonNull Context context,
                 @NonNull WorkDatabase database,
@@ -333,8 +336,8 @@
             return this;
         }
 
-        Builder withScheduler(Scheduler scheduler) {
-            mScheduler = scheduler;
+        Builder withSchedulers(List<Scheduler> schedulers) {
+            mSchedulers = schedulers;
             return this;
         }
 
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/greedy/GreedyScheduler.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/greedy/GreedyScheduler.java
new file mode 100644
index 0000000..acaa2d5
--- /dev/null
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/greedy/GreedyScheduler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.arch.background.workmanager.impl.background.greedy;
+
+import android.arch.background.workmanager.State;
+import android.arch.background.workmanager.impl.Scheduler;
+import android.arch.background.workmanager.impl.WorkManagerImpl;
+import android.arch.background.workmanager.impl.logger.Logger;
+import android.arch.background.workmanager.impl.model.WorkSpec;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+/**
+ * A greedy {@link Scheduler} that schedules unconstrained, non-timed work.  It intentionally does
+ * not acquire any WakeLocks, instead trying to brute-force them as time allows before the process
+ * gets killed.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class GreedyScheduler implements Scheduler {
+
+    private static final String TAG = "GreedyScheduler";
+
+    private WorkManagerImpl mWorkManagerImpl;
+
+    public GreedyScheduler(WorkManagerImpl workManagerImpl) {
+        mWorkManagerImpl = workManagerImpl;
+    }
+
+    @Override
+    public void schedule(WorkSpec... workSpecs) {
+        for (WorkSpec workSpec : workSpecs) {
+            if (workSpec.getState() == State.ENQUEUED
+                    && !workSpec.isPeriodic()
+                    && workSpec.getInitialDelay() == 0L
+                    && !workSpec.hasConstraints()) {
+                Logger.debug(TAG, "Scheduling work ID %s", workSpec.getId());
+                mWorkManagerImpl.startWork(workSpec.getId());
+            }
+        }
+    }
+
+    @Override
+    public void cancel(@NonNull String workSpecId) {
+        Logger.debug(TAG, "Cancelling work ID %s", workSpecId);
+        mWorkManagerImpl.stopWork(workSpecId);
+    }
+}
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmScheduler.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmScheduler.java
index 7bde0b8..865b76d 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmScheduler.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemalarm/SystemAlarmScheduler.java
@@ -24,9 +24,6 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 
-import java.util.HashSet;
-import java.util.Set;
-
 /**
  * A {@link Scheduler} that schedules work using {@link android.app.AlarmManager}.
  *
@@ -38,11 +35,9 @@
     private static final String TAG = "SystemAlarmScheduler";
 
     private final Context mContext;
-    private Set<String> mCancelledIds;
 
     public SystemAlarmScheduler(@NonNull Context context) {
         mContext = context.getApplicationContext();
-        mCancelledIds = new HashSet<>();
     }
 
     @Override
@@ -55,16 +50,10 @@
     @Override
     public void cancel(@NonNull String workSpecId) {
         //TODO (rahulrav@) Store mapping alarm ids along / in a separate table
-        mCancelledIds.add(workSpecId);
         Intent cancelIntent = CommandHandler.createStopWorkIntent(mContext, workSpecId);
         mContext.startService(cancelIntent);
     }
 
-    @Override
-    public boolean isCancelled(@NonNull String workSpecId) {
-        return mCancelledIds.contains(workSpecId);
-    }
-
     /**
      * Periodic work is rescheduled using one-time alarms after each run. This allows the delivery
      * times to drift to guarantee that the interval duration always elapses between alarms.
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobScheduler.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobScheduler.java
index 70741d4..004473a 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobScheduler.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobScheduler.java
@@ -26,9 +26,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 
-import java.util.HashSet;
 import java.util.List;
-import java.util.Set;
 
 /**
  * A class that schedules work using {@link android.app.job.JobScheduler}.
@@ -43,12 +41,10 @@
 
     private JobScheduler mJobScheduler;
     private SystemJobInfoConverter mSystemJobInfoConverter;
-    private Set<String> mCancelledIds;
 
     public SystemJobScheduler(Context context) {
         mJobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
         mSystemJobInfoConverter = new SystemJobInfoConverter(context);
-        mCancelledIds = new HashSet<>();
     }
 
     @Override
@@ -62,7 +58,6 @@
 
     @Override
     public void cancel(@NonNull String workSpecId) {
-        mCancelledIds.add(workSpecId);
         // Note: despite what the word "pending" and the associated Javadoc might imply, this is
         // actually a list of all unfinished jobs that JobScheduler knows about for the current
         // process.
@@ -75,9 +70,4 @@
             }
         }
     }
-
-    @Override
-    public boolean isCancelled(@NonNull String workSpecId) {
-        return mCancelledIds.contains(workSpecId);
-    }
 }
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobService.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobService.java
index 8a9d391..1db061c 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobService.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/background/systemjob/SystemJobService.java
@@ -20,7 +20,6 @@
 import android.app.job.JobParameters;
 import android.app.job.JobService;
 import android.arch.background.workmanager.impl.ExecutionListener;
-import android.arch.background.workmanager.impl.Scheduler;
 import android.arch.background.workmanager.impl.WorkManagerImpl;
 import android.arch.background.workmanager.impl.logger.Logger;
 import android.os.PersistableBundle;
@@ -41,7 +40,6 @@
 public class SystemJobService extends JobService implements ExecutionListener {
     private static final String TAG = "SystemJobService";
     private WorkManagerImpl mWorkManagerImpl;
-    private Scheduler mScheduler;
     private final Map<String, JobParameters> mJobParameters = new HashMap<>();
 
     @Override
@@ -49,7 +47,6 @@
         super.onCreate();
         mWorkManagerImpl = WorkManagerImpl.getInstance();
         mWorkManagerImpl.getProcessor().addExecutionListener(this);
-        mScheduler = mWorkManagerImpl.getBackgroundScheduler();
     }
 
     @Override
@@ -97,7 +94,7 @@
             mJobParameters.remove(workSpecId);
         }
         mWorkManagerImpl.stopWork(workSpecId);
-        return !mScheduler.isCancelled(workSpecId);
+        return !mWorkManagerImpl.getProcessor().isCancelled(workSpecId);
     }
 
     @Override
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/CancelWorkRunnable.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/CancelWorkRunnable.java
index 0fe28a5..55b64fc 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/CancelWorkRunnable.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/CancelWorkRunnable.java
@@ -21,6 +21,8 @@
 import static android.arch.background.workmanager.State.SUCCEEDED;
 
 import android.arch.background.workmanager.State;
+import android.arch.background.workmanager.impl.Processor;
+import android.arch.background.workmanager.impl.Scheduler;
 import android.arch.background.workmanager.impl.WorkDatabase;
 import android.arch.background.workmanager.impl.WorkManagerImpl;
 import android.arch.background.workmanager.impl.model.DependencyDao;
@@ -72,8 +74,14 @@
 
     private void cancel(String workSpecId) {
         recursivelyCancelWorkAndDependencies(workSpecId);
-        mWorkManagerImpl.getProcessor().stopWork(workSpecId, true);
-        mWorkManagerImpl.getBackgroundScheduler().cancel(workSpecId);
+
+        Processor processor = mWorkManagerImpl.getProcessor();
+        processor.stopWork(workSpecId, true);
+        processor.setCancelled(workSpecId);
+
+        for (Scheduler scheduler : mWorkManagerImpl.getSchedulers()) {
+            scheduler.cancel(workSpecId);
+        }
     }
 
     private void recursivelyCancelWorkAndDependencies(String workSpecId) {
diff --git a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/EnqueueRunnable.java b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/EnqueueRunnable.java
index c09cb9b..609c62c 100644
--- a/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/EnqueueRunnable.java
+++ b/background/workmanager/src/main/java/android/arch/background/workmanager/impl/utils/EnqueueRunnable.java
@@ -26,6 +26,7 @@
 import android.arch.background.workmanager.BaseWork;
 import android.arch.background.workmanager.ExistingWorkPolicy;
 import android.arch.background.workmanager.impl.InternalWorkImpl;
+import android.arch.background.workmanager.impl.Scheduler;
 import android.arch.background.workmanager.impl.WorkContinuationImpl;
 import android.arch.background.workmanager.impl.WorkDatabase;
 import android.arch.background.workmanager.impl.WorkManagerImpl;
@@ -92,10 +93,10 @@
     @VisibleForTesting
     public void scheduleWorkInBackground() {
         // Schedule in the background. This list contains work that does not have prerequisites.
-        for (InternalWorkImpl work : mWorkToBeScheduled) {
-            mWorkContinuation.getWorkManagerImpl()
-                    .getBackgroundScheduler()
-                    .schedule(work.getWorkSpec());
+        for (Scheduler scheduler : mWorkContinuation.getWorkManagerImpl().getSchedulers()) {
+            for (InternalWorkImpl work : mWorkToBeScheduled) {
+                scheduler.schedule(work.getWorkSpec());
+            }
         }
     }