Snap for 4832339 from f0a0a39e63da4265934e8bead2ddc619465371cd to pi-release

Change-Id: If085d5207d41cfdc6d1a3bbcf62106ae510dad29
diff --git a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
index a40d6d9..62e8c5b 100644
--- a/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/PublishDocsRules.kt
@@ -24,7 +24,7 @@
 import androidx.build.Strategy.Ignore
 
 val RELEASE_RULE = docsRules("public") {
-    val defaultVersion = "1.0.0-alpha1"
+    val defaultVersion = "1.0.0-alpha3"
     prebuilts(LibraryGroups.ANNOTATION, defaultVersion)
     prebuilts(LibraryGroups.APPCOMPAT, defaultVersion)
     prebuilts(LibraryGroups.ASYNCLAYOUTINFLATER, defaultVersion)
@@ -33,9 +33,7 @@
             .addStubs("car/car-stubs/android.car.jar")
     prebuilts(LibraryGroups.CARDVIEW, defaultVersion)
     prebuilts(LibraryGroups.COLLECTION, defaultVersion)
-    // misses prebuilts, because it was released under different name in alpha1
-    tipOfTree(LibraryGroups.CONTENTPAGER)
-
+    prebuilts(LibraryGroups.CONTENTPAGER, defaultVersion)
     prebuilts(LibraryGroups.COORDINATORLAYOUT, defaultVersion)
     prebuilts(LibraryGroups.CORE, defaultVersion)
     prebuilts(LibraryGroups.CURSORADAPTER, defaultVersion)
@@ -57,7 +55,6 @@
     prebuilts(LibraryGroups.MEDIAROUTER, defaultVersion)
     prebuilts(LibraryGroups.PALETTE, defaultVersion)
     prebuilts(LibraryGroups.PERCENTLAYOUT, defaultVersion)
-    ignore(LibraryGroups.PREFERENCE, "preference-ktx")
     prebuilts(LibraryGroups.PREFERENCE, defaultVersion)
     prebuilts(LibraryGroups.PRINT, defaultVersion)
     prebuilts(LibraryGroups.RECOMMENDATION, defaultVersion)
@@ -76,17 +73,11 @@
     val flatfootVersion = "2.0.0-alpha1"
     prebuilts(LibraryGroups.ROOM, flatfootVersion)
     prebuilts(LibraryGroups.PERSISTENCE, flatfootVersion)
-    // lifecycle-viewmodel-ktx / lifecycle-process / lifecycle-service miss their prebuilts
-    tipOfTree(LibraryGroups.LIFECYCLE, "lifecycle-viewmodel-ktx")
-    tipOfTree(LibraryGroups.LIFECYCLE, "lifecycle-process")
-    tipOfTree(LibraryGroups.LIFECYCLE, "lifecycle-service")
     prebuilts(LibraryGroups.LIFECYCLE, flatfootVersion)
     prebuilts(LibraryGroups.ARCH_CORE, flatfootVersion)
-    prebuilts(LibraryGroups.PAGING, "paging-rxjava2", "1.0.0-alpha1")
-    prebuilts(LibraryGroups.PAGING, "2.0.0-alpha1")
-    // navigation & workmanager don't have prebuilts currently
-    tipOfTree(LibraryGroups.NAVIGATION)
-    tipOfTree(LibraryGroups.WORKMANAGER)
+    prebuilts(LibraryGroups.PAGING, flatfootVersion)
+    prebuilts(LibraryGroups.NAVIGATION, "1.0.0-alpha01")
+    prebuilts(LibraryGroups.WORKMANAGER, "1.0.0-alpha02")
     default(Ignore)
 }
 
diff --git a/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java b/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java
index f17fde4..5e549d2 100644
--- a/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java
+++ b/work/workmanager-firebase/src/main/java/androidx/work/impl/background/firebase/FirebaseDelayedJobAlarmReceiver.java
@@ -21,6 +21,7 @@
 import android.support.annotation.RestrictTo;
 import android.util.Log;
 
+import androidx.work.Configuration;
 import androidx.work.impl.Schedulers;
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
@@ -41,6 +42,7 @@
         final PendingResult pendingResult = goAsync();
         final String workSpecId = intent.getStringExtra(WORKSPEC_ID_KEY);
         final WorkManagerImpl workManagerImpl = WorkManagerImpl.getInstance();
+        final Configuration configuration = workManagerImpl.getConfiguration();
         final WorkDatabase database = workManagerImpl.getWorkDatabase();
         // TODO (rahulrav@) Use WorkManager's task executor here instead.
         new Thread(new Runnable() {
@@ -48,7 +50,7 @@
             public void run() {
                 WorkSpec workSpec = database.workSpecDao().getWorkSpec(workSpecId);
                 if (workSpec != null) {
-                    Schedulers.schedule(database, workManagerImpl.getSchedulers());
+                    Schedulers.schedule(configuration, database, workManagerImpl.getSchedulers());
                 } else {
                     Log.e(TAG, "WorkSpec not found! Cannot schedule!");
                 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index 1c1893f..c131117 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -16,12 +16,15 @@
 
 package androidx.work;
 
+import static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
 import android.arch.persistence.room.testing.MigrationTestHelper;
+import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.support.test.InstrumentationRegistry;
@@ -30,6 +33,9 @@
 
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkDatabaseMigrations;
+import androidx.work.impl.model.WorkSpec;
+import androidx.work.impl.model.WorkTypeConverters;
+import androidx.work.worker.TestWorker;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -53,12 +59,12 @@
 
     // Queries
     private static final String INSERT_ALARM_INFO = "INSERT INTO alarmInfo VALUES (?, ?)";
-    private static final String INSERT_SYSTEM_ID_INFO = "INSERT INTO systemIdInfo VALUES (?, ?)";
-    private static final String CHECK_SYSTEM_ID_INFO = "SELECT * FROM systemIdInfo";
+    private static final String INSERT_SYSTEM_ID_INFO = "INSERT INTO SystemIdInfo VALUES (?, ?)";
+    private static final String CHECK_SYSTEM_ID_INFO = "SELECT * FROM SystemIdInfo";
     private static final String CHECK_ALARM_INFO = "SELECT * FROM alarmInfo";
     private static final String CHECK_TABLE_NAME = "SELECT * FROM %s";
     private static final String TABLE_ALARM_INFO = "alarmInfo";
-    private static final String TABLE_SYSTEM_ID_INFO = "systemIdInfo";
+    private static final String TABLE_SYSTEM_ID_INFO = "SystemIdInfo";
     private static final String TABLE_WORKSPEC = "WorkSpec";
     private static final String TABLE_WORKTAG = "WorkTag";
     private static final String TABLE_WORKNAME = "WorkName";
@@ -86,6 +92,33 @@
         SupportSQLiteDatabase database =
                 mMigrationTestHelper.createDatabase(TEST_DATABASE, OLD_VERSION);
 
+        String workSpecId0 = UUID.randomUUID().toString();
+        ContentValues contentValues = new ContentValues();
+        contentValues.put("id", workSpecId0);
+        contentValues.put("state", WorkTypeConverters.StateIds.ENQUEUED);
+        contentValues.put("worker_class_name", TestWorker.class.getName());
+        contentValues.put("input_merger_class_name", OverwritingInputMerger.class.getName());
+        contentValues.put("input", Data.toByteArray(Data.EMPTY));
+        contentValues.put("output", Data.toByteArray(Data.EMPTY));
+        contentValues.put("initial_delay", 0L);
+        contentValues.put("interval_duration", 0L);
+        contentValues.put("flex_duration", 0L);
+        contentValues.put("required_network_type", false);
+        contentValues.put("requires_charging", false);
+        contentValues.put("requires_device_idle", false);
+        contentValues.put("requires_battery_not_low", false);
+        contentValues.put("requires_storage_not_low", false);
+        contentValues.put("content_uri_triggers",
+                WorkTypeConverters.contentUriTriggersToByteArray(new ContentUriTriggers()));
+        contentValues.put("run_attempt_count", 0);
+        contentValues.put("backoff_policy",
+                WorkTypeConverters.backoffPolicyToInt(BackoffPolicy.EXPONENTIAL));
+        contentValues.put("backoff_delay_duration", WorkRequest.DEFAULT_BACKOFF_DELAY_MILLIS);
+        contentValues.put("period_start_time", 0L);
+        contentValues.put("minimum_retention_duration", 0L);
+        contentValues.put("schedule_requested_at", WorkSpec.SCHEDULE_NOT_REQUESTED_YET);
+        database.insert("workspec", CONFLICT_FAIL, contentValues);
+
         String workSpecId1 = UUID.randomUUID().toString();
         String workSpecId2 = UUID.randomUUID().toString();
 
@@ -101,6 +134,14 @@
                 VALIDATE_DROPPED_TABLES,
                 WorkDatabaseMigrations.MIGRATION_1_2);
 
+        Cursor tagCursor = database.query("SELECT * FROM worktag");
+        assertThat(tagCursor.getCount(), is(1));
+        tagCursor.moveToFirst();
+        assertThat(tagCursor.getString(tagCursor.getColumnIndex("tag")),
+                is(TestWorker.class.getName()));
+        assertThat(tagCursor.getString(tagCursor.getColumnIndex("work_spec_id")), is(workSpecId0));
+        tagCursor.close();
+
         Cursor cursor = database.query(CHECK_SYSTEM_ID_INFO);
         assertThat(cursor.getCount(), is(2));
         cursor.moveToFirst();
@@ -127,7 +168,7 @@
         String workSpecId1 = UUID.randomUUID().toString();
         String workSpecId2 = UUID.randomUUID().toString();
 
-        // insert systemIdInfo
+        // insert SystemIdInfo
         database.execSQL(INSERT_SYSTEM_ID_INFO, new Object[]{workSpecId1, 1});
         database.execSQL(INSERT_SYSTEM_ID_INFO, new Object[]{workSpecId2, 2});
 
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java
index da5a8e0..6dd23cb 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkSpecDaoTest.java
@@ -19,6 +19,7 @@
 import static androidx.work.State.BLOCKED;
 import static androidx.work.State.FAILED;
 import static androidx.work.State.SUCCEEDED;
+import static androidx.work.impl.Scheduler.MAX_SCHEDULER_LIMIT;
 
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
@@ -70,7 +71,9 @@
         WorkSpecDao workSpecDao = mDatabase.workSpecDao();
         // Treat the scheduled request as previously scheduled
         workSpecDao.markWorkSpecScheduled(scheduled.getStringId(), System.currentTimeMillis());
-        List<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling();
+        List<WorkSpec> eligibleWorkSpecs =
+                workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT);
+
         assertThat(eligibleWorkSpecs.size(), equalTo(2));
         assertThat(eligibleWorkSpecs,
                 containsInAnyOrder(work.getWorkSpec(), enqueued.getWorkSpec()));
@@ -100,7 +103,8 @@
         insertWork(succeeded);
         insertWork(failed);
 
-        List<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling();
+        List<WorkSpec> eligibleWorkSpecs =
+                workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT);
         assertThat(eligibleWorkSpecs, notNullValue());
         assertThat(eligibleWorkSpecs.size(), is(1));
         assertThat(eligibleWorkSpecs, containsInAnyOrder(enqueued.getWorkSpec()));
@@ -131,7 +135,8 @@
         insertWork(succeeded);
         insertWork(failed);
 
-        List<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling();
+        List<WorkSpec> eligibleWorkSpecs =
+                workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT);
         assertThat(eligibleWorkSpecs, notNullValue());
         assertThat(eligibleWorkSpecs.size(), is(0));
     }
@@ -171,7 +176,8 @@
 
         workSpecDao.resetScheduledState();
 
-        List<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling();
+        List<WorkSpec> eligibleWorkSpecs =
+                workSpecDao.getEligibleWorkForScheduling(MAX_SCHEDULER_LIMIT);
         assertThat(eligibleWorkSpecs.size(), is(1));
         // Not using contains in any order as the scheduleRequestedAt changes post reset.
         assertThat(eligibleWorkSpecs.get(0).id, is(enqueued.getStringId()));
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java
index cfaa1f3..1e178b1 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/ProcessorTest.java
@@ -25,6 +25,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.work.Configuration;
 import androidx.work.DatabaseTest;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.worker.InfiniteTestWorker;
@@ -43,8 +44,10 @@
     @Before
     public void setUp() {
         Context appContext = InstrumentationRegistry.getTargetContext().getApplicationContext();
+        Configuration configuration = new Configuration.Builder().build();
         mProcessor = new Processor(
                 appContext,
+                configuration,
                 mDatabase,
                 Collections.singletonList(mock(Scheduler.class)),
                 Executors.newSingleThreadScheduledExecutor()) {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index 7783208..f4a3a7b 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -72,6 +72,7 @@
 @SmallTest
 public class WorkContinuationImplTest extends WorkManagerTest {
 
+    private Configuration mConfiguration;
     private WorkDatabase mDatabase;
     private WorkManagerImpl mWorkManagerImpl;
     private Scheduler mScheduler;
@@ -103,11 +104,11 @@
 
         mScheduler = mock(Scheduler.class);
         Context context = InstrumentationRegistry.getTargetContext();
-        Configuration configuration = new Configuration.Builder()
+        mConfiguration = new Configuration.Builder()
                 .setExecutor(Executors.newSingleThreadExecutor())
                 .build();
 
-        mWorkManagerImpl = spy(new WorkManagerImpl(context, configuration));
+        mWorkManagerImpl = spy(new WorkManagerImpl(context, mConfiguration));
         when(mWorkManagerImpl.getSchedulers()).thenReturn(Collections.singletonList(mScheduler));
         WorkManagerImpl.setDelegate(mWorkManagerImpl);
         mDatabase = mWorkManagerImpl.getWorkDatabase();
@@ -306,7 +307,8 @@
 
         // TODO(sumir): I can't seem to get this kicked off automatically, so I'm running it myself.
         // Figure out what's going on here.
-        new WorkerWrapper.Builder(InstrumentationRegistry.getTargetContext(), mDatabase, joinId)
+        Context context = InstrumentationRegistry.getTargetContext();
+        new WorkerWrapper.Builder(context, mConfiguration, mDatabase, joinId)
                 .build()
                 .run();
 
@@ -514,6 +516,7 @@
     }
 
     private static void verifyScheduled(Scheduler scheduler, WorkContinuationImpl continuation) {
+        Configuration configuration = continuation.getWorkManagerImpl().getConfiguration();
         ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
         verify(scheduler, times(1)).schedule(captor.capture());
         List<WorkSpec> workSpecs = captor.getAllValues();
@@ -521,7 +524,10 @@
 
         WorkDatabase workDatabase = continuation.getWorkManagerImpl().getWorkDatabase();
         List<WorkSpec> eligibleWorkSpecs =
-                workDatabase.workSpecDao().getEligibleWorkForScheduling();
+                workDatabase
+                        .workSpecDao()
+                        .getEligibleWorkForScheduling(
+                                configuration.getMaxSchedulerLimit());
 
         Set<String> capturedIds = new HashSet<>();
         for (WorkSpec workSpec : workSpecs) {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
index 51e98e5..72ebe94 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -16,25 +16,33 @@
 
 package androidx.work.impl;
 
+import static androidx.work.worker.CheckLimitsWorker.KEY_EXCEEDS_SCHEDULER_LIMIT;
+import static androidx.work.worker.CheckLimitsWorker.KEY_LIMIT_TO_ENFORCE;
+
+import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
+import android.arch.lifecycle.Observer;
 import android.content.Context;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
 import android.support.test.runner.AndroidJUnit4;
 
 import androidx.work.Configuration;
+import androidx.work.Data;
 import androidx.work.OneTimeWorkRequest;
-import androidx.work.impl.model.WorkSpec;
+import androidx.work.TestLifecycleOwner;
+import androidx.work.WorkContinuation;
+import androidx.work.WorkStatus;
 import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule;
-import androidx.work.worker.TestWorker;
+import androidx.work.worker.CheckLimitsWorker;
 
 import org.junit.After;
 import org.junit.Before;
@@ -42,16 +50,23 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class WorkManagerImplLargeExecutorTest {
 
     private static final int NUM_WORKERS = 500;
+    private static final int TEST_TIMEOUT_SECONDS = 30;
 
     // ThreadPoolExecutor parameters.
     private static final int MIN_POOL_SIZE = 0;
@@ -61,6 +76,7 @@
     private static final long KEEP_ALIVE_TIME = 2L;
 
     private WorkManagerImpl mWorkManagerImpl;
+    private TestLifecycleOwner mLifecycleOwner;
 
     @Rule
     public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
@@ -90,8 +106,10 @@
                 MIN_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, SECONDS, queue);
         Configuration configuration = new Configuration.Builder()
                 .setExecutor(executor)
+                .setMaxSchedulerLimit(50)
                 .build();
-        mWorkManagerImpl = new WorkManagerImpl(context, configuration);
+        mWorkManagerImpl = new WorkManagerImpl(context, configuration, true);
+        mLifecycleOwner = new TestLifecycleOwner();
         WorkManagerImpl.setDelegate(mWorkManagerImpl);
     }
 
@@ -104,15 +122,57 @@
     @Test
     @LargeTest
     @SdkSuppress(maxSdkVersion = 22)
-    public void testSchedulerLimits() {
-        for (int i = 0; i < NUM_WORKERS; i++) {
-            mWorkManagerImpl.enqueue(OneTimeWorkRequest.from(TestWorker.class));
-            List<WorkSpec> eligibleWorkSpecs = mWorkManagerImpl.getWorkDatabase()
-                    .workSpecDao()
-                    .getEligibleWorkForScheduling();
+    public void testSchedulerLimits() throws InterruptedException {
+        List<OneTimeWorkRequest> workRequests = new ArrayList<>(NUM_WORKERS);
+        final Set<UUID> completed = new HashSet<>(NUM_WORKERS);
+        final int schedulerLimit = mWorkManagerImpl
+                .getConfiguration()
+                .getMaxSchedulerLimit();
 
-            int size = eligibleWorkSpecs != null ? eligibleWorkSpecs.size() : 0;
-            assertThat(size, lessThanOrEqualTo(Scheduler.MAX_SCHEDULER_LIMIT));
+        final Data input = new Data.Builder()
+                .putInt(KEY_LIMIT_TO_ENFORCE, schedulerLimit)
+                .build();
+
+        for (int i = 0; i < NUM_WORKERS; i++) {
+            OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(CheckLimitsWorker.class)
+                    .setInputData(input)
+                    .build();
+
+            workRequests.add(request);
         }
+
+
+        final CountDownLatch latch = new CountDownLatch(NUM_WORKERS);
+        WorkContinuation continuation = mWorkManagerImpl.beginWith(workRequests);
+
+        continuation.getStatuses()
+                .observe(mLifecycleOwner, new Observer<List<WorkStatus>>() {
+                    @Override
+                    public void onChanged(@Nullable List<WorkStatus> workStatuses) {
+                        if (workStatuses == null || workStatuses.isEmpty()) {
+                            return;
+                        }
+
+                        for (WorkStatus workStatus: workStatuses) {
+                            if (workStatus.getState().isFinished()) {
+
+                                Data output = workStatus.getOutputData();
+
+                                boolean exceededLimits = output.getBoolean(
+                                        KEY_EXCEEDS_SCHEDULER_LIMIT, true);
+
+                                assertThat(exceededLimits, is(false));
+                                if (!completed.contains(workStatus.getId())) {
+                                    completed.add(workStatus.getId());
+                                    latch.countDown();
+                                }
+                            }
+                        }
+                    }
+                });
+
+        continuation.enqueue();
+        latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertThat(latch.getCount(), is(0L));
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index 7168600..3e01164 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -171,6 +171,18 @@
 
     @Test
     @SmallTest
+    public void testEnqueue_AddsImplicitTags() {
+        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        mWorkManagerImpl.synchronous().enqueueSync(work);
+
+        WorkTagDao workTagDao = mDatabase.workTagDao();
+        List<String> tags = workTagDao.getTagsForWorkSpecId(work.getStringId());
+        assertThat(tags, is(notNullValue()));
+        assertThat(tags, contains(TestWorker.class.getName()));
+    }
+
+    @Test
+    @SmallTest
     public void testEnqueue_insertMultipleWork() {
         OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
@@ -841,12 +853,12 @@
                 work0.getId(),
                 ENQUEUED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(TestWorker.class.getName()));
         WorkStatus workStatus1 = new WorkStatus(
                 work1.getId(),
                 ENQUEUED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(TestWorker.class.getName()));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
 
         WorkSpecDao workSpecDao = mDatabase.workSpecDao();
@@ -860,7 +872,7 @@
                 work0.getId(),
                 RUNNING,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(TestWorker.class.getName()));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
 
         clearInvocations(mockObserver);
@@ -874,7 +886,7 @@
                 work1.getId(),
                 RUNNING,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(TestWorker.class.getName()));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
 
         liveData.removeObservers(testLifecycleOwner);
@@ -907,17 +919,17 @@
                 work0.getId(),
                 RUNNING,
                 Data.EMPTY,
-                Arrays.asList(firstTag, secondTag));
+                Arrays.asList(TestWorker.class.getName(), firstTag, secondTag));
         WorkStatus workStatus1 = new WorkStatus(
                 work1.getId(),
                 BLOCKED,
                 Data.EMPTY,
-                Collections.singletonList(firstTag));
+                Arrays.asList(TestWorker.class.getName(), firstTag));
         WorkStatus workStatus2 = new WorkStatus(
                 work2.getId(),
                 SUCCEEDED,
                 Data.EMPTY,
-                Collections.singletonList(secondTag));
+                Arrays.asList(TestWorker.class.getName(), secondTag));
 
         List<WorkStatus> workStatuses = mWorkManagerImpl.getStatusesByTagSync(firstTag);
         assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1));
@@ -969,12 +981,12 @@
                 work0.getId(),
                 RUNNING,
                 Data.EMPTY,
-                Arrays.asList(firstTag, secondTag));
+                Arrays.asList(TestWorker.class.getName(), firstTag, secondTag));
         WorkStatus workStatus1 = new WorkStatus(
                 work1.getId(),
                 BLOCKED,
                 Data.EMPTY,
-                Collections.singletonList(firstTag));
+                Arrays.asList(TestWorker.class.getName(), firstTag));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
 
         workSpecDao.setState(ENQUEUED, work0.getStringId());
@@ -987,7 +999,7 @@
                 work0.getId(),
                 ENQUEUED,
                 Data.EMPTY,
-                Arrays.asList(firstTag, secondTag));
+                Arrays.asList(TestWorker.class.getName(), firstTag, secondTag));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1));
 
         liveData.removeObservers(testLifecycleOwner);
@@ -1015,17 +1027,17 @@
                 work0.getId(),
                 RUNNING,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
         WorkStatus workStatus1 = new WorkStatus(
                 work1.getId(),
                 BLOCKED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
         WorkStatus workStatus2 = new WorkStatus(
                 work2.getId(),
                 BLOCKED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
 
         List<WorkStatus> workStatuses = mWorkManagerImpl.getStatusesForUniqueWorkSync(uniqueName);
         assertThat(workStatuses, containsInAnyOrder(workStatus0, workStatus1, workStatus2));
@@ -1069,17 +1081,17 @@
                 work0.getId(),
                 RUNNING,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
         WorkStatus workStatus1 = new WorkStatus(
                 work1.getId(),
                 BLOCKED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
         WorkStatus workStatus2 = new WorkStatus(
                 work2.getId(),
                 BLOCKED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2));
 
         workSpecDao.setState(ENQUEUED, work0.getStringId());
@@ -1092,7 +1104,7 @@
                 work0.getId(),
                 ENQUEUED,
                 Data.EMPTY,
-                Collections.<String>emptyList());
+                Collections.singletonList(InfiniteTestWorker.class.getName()));
         assertThat(captor.getValue(), containsInAnyOrder(workStatus0, workStatus1, workStatus2));
 
         liveData.removeObservers(testLifecycleOwner);
@@ -1333,6 +1345,38 @@
 
     @Test
     @SmallTest
+    public void pruneFinishedWork() {
+        OneTimeWorkRequest enqueuedWork = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        OneTimeWorkRequest finishedWork =
+                new OneTimeWorkRequest.Builder(TestWorker.class).setInitialState(SUCCEEDED).build();
+        OneTimeWorkRequest finishedWorkWithUnfinishedDependent =
+                new OneTimeWorkRequest.Builder(TestWorker.class).setInitialState(SUCCEEDED).build();
+        OneTimeWorkRequest finishedWorkWithLongKeepForAtLeast =
+                new OneTimeWorkRequest.Builder(TestWorker.class)
+                        .setInitialState(SUCCEEDED)
+                        .keepResultsForAtLeast(999, TimeUnit.DAYS)
+                        .build();
+
+        insertWorkSpecAndTags(enqueuedWork);
+        insertWorkSpecAndTags(finishedWork);
+        insertWorkSpecAndTags(finishedWorkWithUnfinishedDependent);
+        insertWorkSpecAndTags(finishedWorkWithLongKeepForAtLeast);
+
+        insertDependency(enqueuedWork, finishedWorkWithUnfinishedDependent);
+
+        mWorkManagerImpl.synchronous().pruneWorkSync();
+
+        WorkSpecDao workSpecDao = mDatabase.workSpecDao();
+        assertThat(workSpecDao.getWorkSpec(enqueuedWork.getStringId()), is(notNullValue()));
+        assertThat(workSpecDao.getWorkSpec(finishedWork.getStringId()), is(nullValue()));
+        assertThat(workSpecDao.getWorkSpec(finishedWorkWithUnfinishedDependent.getStringId()),
+                is(notNullValue()));
+        assertThat(workSpecDao.getWorkSpec(finishedWorkWithLongKeepForAtLeast.getStringId()),
+                is(nullValue()));
+    }
+
+    @Test
+    @SmallTest
     public void testSynchronousCancelAndGetStatus() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWorkSpecAndTags(work);
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 4a50d10..739fcf7 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -44,6 +44,7 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import androidx.work.ArrayCreatingInputMerger;
+import androidx.work.Configuration;
 import androidx.work.Data;
 import androidx.work.DatabaseTest;
 import androidx.work.OneTimeWorkRequest;
@@ -78,6 +79,7 @@
 
 @RunWith(AndroidJUnit4.class)
 public class WorkerWrapperTest extends DatabaseTest {
+    private Configuration mConfiguration;
     private WorkSpecDao mWorkSpecDao;
     private DependencyDao mDependencyDao;
     private Context mContext;
@@ -90,6 +92,7 @@
     @Before
     public void setUp() {
         mContext = InstrumentationRegistry.getTargetContext();
+        mConfiguration = new Configuration.Builder().build();
         mWorkSpecDao = spy(mDatabase.workSpecDao());
         mDependencyDao = mDatabase.dependencyDao();
         mMockListener = mock(ExecutionListener.class);
@@ -101,7 +104,7 @@
     public void testSuccess() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -114,7 +117,7 @@
     public void testRunAttemptCountIncremented_successfulExecution() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .withListener(mMockListener)
                 .build()
@@ -128,7 +131,7 @@
     public void testRunAttemptCountIncremented_failedExecution() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .withListener(mMockListener)
                 .build()
@@ -141,7 +144,7 @@
     @SmallTest
     public void testPermanentErrorWithInvalidWorkSpecId() {
         final String invalidWorkSpecId = "INVALID_ID";
-        new WorkerWrapper.Builder(mContext, mDatabase, invalidWorkSpecId)
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, invalidWorkSpecId)
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -155,7 +158,7 @@
                 .setInitialState(RUNNING)
                 .build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -169,7 +172,7 @@
                 .setInitialState(CANCELLED)
                 .build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -183,7 +186,7 @@
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         getWorkSpec(work).workerClassName = "INVALID_CLASS_NAME";
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -197,7 +200,7 @@
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         getWorkSpec(work).inputMergerClassName = "INVALID_CLASS_NAME";
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .withListener(mMockListener)
                 .build()
@@ -211,7 +214,7 @@
     public void testFailed() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -224,7 +227,8 @@
     public void testRunning() throws InterruptedException {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(SleepTestWorker.class).build();
         insertWork(work);
-        WorkerWrapper wrapper = new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        WorkerWrapper wrapper = new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                work.getStringId())
                 .withListener(mMockListener)
                 .build();
         Executors.newSingleThreadExecutor().submit(wrapper);
@@ -241,7 +245,7 @@
                 .setInitialState(RUNNING)
                 .build();
         insertWork(work);
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -271,7 +275,8 @@
         assertThat(mWorkSpecDao.getState(work.getStringId()), is(BLOCKED));
         assertThat(mDependencyDao.hasCompletedAllPrerequisites(work.getStringId()), is(false));
 
-        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                prerequisiteWork.getStringId())
                 .withListener(mMockListener)
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build()
@@ -306,7 +311,8 @@
             mDatabase.endTransaction();
         }
 
-        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                prerequisiteWork.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build().run();
 
@@ -349,17 +355,19 @@
         }
 
         // Run the prerequisites.
-        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork1.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                prerequisiteWork1.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build().run();
 
-        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork2.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                prerequisiteWork2.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build().run();
 
         // Create and run the dependent work.
         WorkerWrapper workerWrapper =
-                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                         .withSchedulers(Collections.singletonList(mMockScheduler))
                         .build();
         workerWrapper.run();
@@ -392,7 +400,8 @@
 
         long beforeUnblockedTime = System.currentTimeMillis();
 
-        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                prerequisiteWork.getStringId())
                 .withListener(mMockListener)
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build()
@@ -429,7 +438,8 @@
             mDatabase.endTransaction();
         }
 
-        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase,
+                prerequisiteWork.getStringId())
                 .build()
                 .run();
 
@@ -452,7 +462,7 @@
 
         insertWork(periodicWork);
 
-        new WorkerWrapper.Builder(mContext, mDatabase, periodicWork.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWork.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -475,7 +485,7 @@
 
         insertWork(periodicWork);
 
-        new WorkerWrapper.Builder(mContext, mDatabase, periodicWork.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWork.getStringId())
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -495,7 +505,7 @@
 
         final String periodicWorkId = periodicWork.getStringId();
         insertWork(periodicWork);
-        new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId)
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId)
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -517,7 +527,7 @@
 
         final String periodicWorkId = periodicWork.getStringId();
         insertWork(periodicWork);
-        new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId)
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId)
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -539,7 +549,7 @@
 
         final String periodicWorkId = periodicWork.getStringId();
         insertWork(periodicWork);
-        new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId)
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId)
                 .withListener(mMockListener)
                 .build()
                 .run();
@@ -557,7 +567,7 @@
         insertWork(work);
         Scheduler mockScheduler = mock(Scheduler.class);
 
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mockScheduler))
                 .build()
                 .run();
@@ -629,8 +639,8 @@
         OneTimeWorkRequest work =
                 new OneTimeWorkRequest.Builder(TestWorker.class).build();
         Extras.RuntimeExtras runtimeExtras = new Extras.RuntimeExtras();
-        runtimeExtras.triggeredContentAuthorities = new String[] { "tca1", "tca2", "tca3" };
-        runtimeExtras.triggeredContentUris = new Uri[] { Uri.parse("tcu1"), Uri.parse("tcu2") };
+        runtimeExtras.triggeredContentAuthorities = new String[]{"tca1", "tca2", "tca3"};
+        runtimeExtras.triggeredContentUris = new Uri[]{Uri.parse("tcu1"), Uri.parse("tcu2")};
 
         Worker worker = WorkerWrapper.workerFromWorkSpec(
                 mContext,
@@ -653,7 +663,7 @@
         OneTimeWorkRequest unscheduled = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(unscheduled);
 
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .withListener(mMockListener)
                 .build()
@@ -673,7 +683,7 @@
         OneTimeWorkRequest unscheduled = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(unscheduled);
 
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .withListener(mMockListener)
                 .build()
@@ -691,7 +701,7 @@
         insertWork(work);
 
         WorkerWrapper workerWrapper =
-                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                         .withSchedulers(Collections.singletonList(mMockScheduler))
                         .withListener(mMockListener)
                         .build();
@@ -717,7 +727,7 @@
         assertThat(worker.isStopped(), is(false));
 
         WorkerWrapper workerWrapper =
-                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                         .withSchedulers(Collections.singletonList(mMockScheduler))
                         .withListener(mMockListener)
                         .withWorker(worker)
@@ -744,7 +754,7 @@
         assertThat(worker.isStopped(), is(false));
 
         WorkerWrapper workerWrapper =
-                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                         .withSchedulers(Collections.singletonList(mMockScheduler))
                         .withListener(mMockListener)
                         .withWorker(worker)
@@ -761,7 +771,7 @@
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ExceptionWorker.class).build();
         insertWork(work);
 
-        new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                 .withSchedulers(Collections.singletonList(mMockScheduler))
                 .withListener(mMockListener)
                 .build()
@@ -777,7 +787,7 @@
         insertWork(work);
 
         WorkerWrapper workerWrapper =
-                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
                         .withSchedulers(Collections.singletonList(mMockScheduler))
                         .withListener(mMockListener)
                         .build();
@@ -797,10 +807,10 @@
         insertWork(work);
 
         WorkerWrapper workerWrapper =
-                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
-                .withSchedulers(Collections.singletonList(mMockScheduler))
-                .withListener(mMockListener)
-                .build();
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
+                        .withSchedulers(Collections.singletonList(mMockScheduler))
+                        .withListener(mMockListener)
+                        .build();
 
         Executors.newSingleThreadExecutor().submit(workerWrapper);
         Thread.sleep(1000L);
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 5e36dfd..cfe2cef 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -34,6 +34,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.work.Configuration;
 import androidx.work.Constraints;
 import androidx.work.DatabaseTest;
 import androidx.work.OneTimeWorkRequest;
@@ -81,6 +82,7 @@
     private Context mContext;
     private Scheduler mScheduler;
     private WorkManagerImpl mWorkManager;
+    private Configuration mConfiguration;
     private ExecutorService mExecutorService;
     private Processor mProcessor;
     private Processor mSpyProcessor;
@@ -108,10 +110,13 @@
             }
         };
 
+        mConfiguration = new Configuration.Builder().build();
         when(mWorkManager.getWorkDatabase()).thenReturn(mDatabase);
+        when(mWorkManager.getConfiguration()).thenReturn(mConfiguration);
         mExecutorService = Executors.newSingleThreadExecutor();
         mProcessor = new Processor(
                 mContext,
+                mConfiguration,
                 mDatabase,
                 Collections.singletonList(mScheduler),
                 // simulate real world use-case
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index a86df66..6f136bf 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -31,6 +31,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import androidx.work.Configuration;
 import androidx.work.Constraints;
 import androidx.work.Data;
 import androidx.work.DatabaseTest;
@@ -76,6 +77,7 @@
     private ExecutorService mExecutorService;
 
     private WorkManagerImpl mWorkManagerImpl;
+    private Configuration mConfiguration;
     private Scheduler mScheduler;
     private Trackers mTracker;
     private BatteryChargingTracker mBatteryChargingTracker;
@@ -89,10 +91,12 @@
         mHandler = new Handler(Looper.getMainLooper());
         mExecutorService = Executors.newSingleThreadScheduledExecutor();
         mLatch = new CountDownLatch(1);
+        mConfiguration = new Configuration.Builder().build();
 
         mWorkManagerImpl = mock(WorkManagerImpl.class);
         mScheduler = mock(Scheduler.class);
         when(mWorkManagerImpl.getWorkDatabase()).thenReturn(mDatabase);
+        when(mWorkManagerImpl.getConfiguration()).thenReturn(mConfiguration);
 
         mBatteryChargingTracker = spy(new BatteryChargingTracker(mContext));
         mBatteryNotLowTracker = spy(new BatteryNotLowTracker(mContext));
@@ -149,7 +153,8 @@
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
 
-        WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId);
+        WorkerWrapper.Builder builder =
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
         builder.withWorker(spyWorker)
                 .withListener(this)
                 .withSchedulers(Collections.singletonList(mScheduler));
@@ -194,7 +199,8 @@
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
 
-        WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId);
+        WorkerWrapper.Builder builder =
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
         builder.withWorker(spyWorker)
                 .withListener(this)
                 .withSchedulers(Collections.singletonList(mScheduler));
@@ -237,8 +243,8 @@
 
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
-
-        WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId);
+        WorkerWrapper.Builder builder =
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
         builder.withWorker(spyWorker)
                 .withListener(this)
                 .withSchedulers(Collections.singletonList(mScheduler));
@@ -290,7 +296,8 @@
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
 
-        WorkerWrapper.Builder builder = new WorkerWrapper.Builder(mContext, mDatabase, workSpecId);
+        WorkerWrapper.Builder builder =
+                new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, workSpecId);
         builder.withWorker(spyWorker)
                 .withListener(this)
                 .withSchedulers(Collections.singletonList(mScheduler));
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java
new file mode 100644
index 0000000..1176364
--- /dev/null
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java
@@ -0,0 +1,56 @@
+/*
+ * 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 androidx.work.worker;
+
+import static androidx.work.Worker.Result.SUCCESS;
+
+import android.support.annotation.NonNull;
+
+import androidx.work.Data;
+import androidx.work.Worker;
+import androidx.work.impl.Scheduler;
+import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.WorkSpec;
+
+import java.util.List;
+
+public class CheckLimitsWorker extends Worker {
+    /* The limit to enforce */
+    public static final String KEY_LIMIT_TO_ENFORCE = "limit";
+
+    /* The output key which tells us if we exceeded the scheduler limits. */
+    public static final String KEY_EXCEEDS_SCHEDULER_LIMIT = "exceed_scheduler_limit";
+
+    @NonNull
+    @Override
+    public Result doWork() {
+        Data input = getInputData();
+        int limitToEnforce = input.getInt(KEY_LIMIT_TO_ENFORCE, Scheduler.MAX_SCHEDULER_LIMIT);
+        WorkManagerImpl workManager = WorkManagerImpl.getInstance();
+        List<WorkSpec> eligibleWorkSpecs = workManager.getWorkDatabase()
+                .workSpecDao()
+                .getEligibleWorkForScheduling(limitToEnforce);
+        int size = eligibleWorkSpecs != null ? eligibleWorkSpecs.size() : 0;
+        boolean exceedsLimits = size > limitToEnforce;
+        Data output = new Data.Builder()
+                .putBoolean(KEY_EXCEEDS_SCHEDULER_LIMIT, exceedsLimits)
+                .build();
+
+        setOutputData(output);
+        return SUCCESS;
+    }
+}
diff --git a/work/workmanager/src/main/java/androidx/work/Configuration.java b/work/workmanager/src/main/java/androidx/work/Configuration.java
index 4fe1470..77f5ffa 100644
--- a/work/workmanager/src/main/java/androidx/work/Configuration.java
+++ b/work/workmanager/src/main/java/androidx/work/Configuration.java
@@ -16,7 +16,11 @@
 
 package androidx.work;
 
+import static androidx.work.impl.Scheduler.MAX_SCHEDULER_LIMIT;
+
+import android.os.Build;
 import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
 
 import androidx.work.impl.utils.IdGenerator;
 
@@ -28,9 +32,16 @@
  */
 public final class Configuration {
 
+    /**
+     * The minimum number of system requests which can be enqueued by {@link WorkManager}
+     * when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}.
+     */
+    public static final int MIN_SCHEDULER_LIMIT = 20;
+
     private final Executor mExecutor;
     private final int mMinJobSchedulerId;
     private final int mMaxJobSchedulerId;
+    private final int mMaxSchedulerLimit;
 
     private Configuration(@NonNull Configuration.Builder builder) {
         if (builder.mExecutor == null) {
@@ -40,6 +51,7 @@
         }
         mMinJobSchedulerId = builder.mMinJobSchedulerId;
         mMaxJobSchedulerId = builder.mMaxJobSchedulerId;
+        mMaxSchedulerLimit = builder.mMaxSchedulerLimit;
     }
 
     /**
@@ -75,6 +87,22 @@
         return mMaxJobSchedulerId;
     }
 
+    /**
+     * @return The maximum number of system requests which can be enqueued by {@link WorkManager}
+     * when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public int getMaxSchedulerLimit() {
+        // We double schedule jobs in SDK 23. So use half the number of max slots specified.
+        if (Build.VERSION.SDK_INT == 23) {
+            return mMaxSchedulerLimit / 2;
+        } else {
+            return mMaxSchedulerLimit;
+        }
+    }
+
     private Executor createDefaultExecutor() {
         return Executors.newFixedThreadPool(
                 // This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
@@ -88,6 +116,7 @@
 
         int mMinJobSchedulerId = IdGenerator.INITIAL_ID;
         int mMaxJobSchedulerId = Integer.MAX_VALUE;
+        int mMaxSchedulerLimit = MIN_SCHEDULER_LIMIT;
         Executor mExecutor;
 
         /**
@@ -122,6 +151,33 @@
         }
 
         /**
+         * Specifies the maximum number of system requests made by {@link WorkManager}
+         * when using {@link android.app.job.JobScheduler} or {@link android.app.AlarmManager}.
+         * When the application exceeds this limit {@link WorkManager} maintains an internal queue
+         * of {@link WorkRequest}s, and enqueues when slots become free.
+         *
+         * {@link WorkManager} requires a minimum of {@link Configuration#MIN_SCHEDULER_LIMIT}
+         * slots. The total number of slots also cannot exceed {@code 100} which is
+         * the {@link android.app.job.JobScheduler} limit.
+         *
+         * @param maxSchedulerLimit The total number of jobs which can be enqueued by
+         *                                {@link WorkManager} when using
+         *                                {@link android.app.job.JobScheduler}.
+         * @return This {@link Builder} instance
+         * @throws IllegalArgumentException when the number of jobs <
+         *                                  {@link Configuration#MIN_SCHEDULER_LIMIT}
+         */
+        public Builder setMaxSchedulerLimit(int maxSchedulerLimit) {
+            if (maxSchedulerLimit < MIN_SCHEDULER_LIMIT) {
+                throw new IllegalArgumentException(
+                        "WorkManager needs to be able to schedule at least 20 jobs in "
+                                + "JobScheduler.");
+            }
+            mMaxSchedulerLimit = Math.min(maxSchedulerLimit, MAX_SCHEDULER_LIMIT);
+            return this;
+        }
+
+        /**
          * Specifies a custom {@link Executor} for WorkManager.
          *
          * @param executor An {@link Executor} for processing work
diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
index f01e363..9f633d8 100644
--- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
@@ -21,6 +21,7 @@
 
 import java.util.List;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Blocking methods for {@link WorkManager} operations.  These methods are expected to be called
@@ -129,6 +130,20 @@
     long getLastCancelAllTimeMillisSync();
 
     /**
+     * Prunes all eligible finished work from the internal database in a synchronous fashion.
+     * Eligible work must be finished ({@link State#SUCCEEDED}, {@link State#FAILED}, or
+     * {@link State#CANCELLED}), with zero unfinished dependents.
+     * <p>
+     * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in
+     * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work.
+     * You do not normally need to call this method - WorkManager takes care to auto-prune its work
+     * after a sane period of time.  This method also ignores the
+     * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy.
+     */
+    @WorkerThread
+    void pruneWorkSync();
+
+    /**
      * Gets the {@link WorkStatus} of a given work id in a synchronous fashion.  This method is
      * expected to be called from a background thread.
      *
diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java
index 83891dc..5960e50 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java
@@ -26,6 +26,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.UUID;
+import java.util.concurrent.TimeUnit;
 
 /**
  * WorkManager is a library used to enqueue work that is guaranteed to execute after its constraints
@@ -288,6 +289,19 @@
     public abstract void cancelAllWork();
 
     /**
+     * Prunes all eligible finished work from the internal database.  Eligible work must be finished
+     * ({@link State#SUCCEEDED}, {@link State#FAILED}, or {@link State#CANCELLED}), with zero
+     * unfinished dependents.
+     * <p>
+     * <b>Use this method with caution</b>; by invoking it, you (and any modules and libraries in
+     * your codebase) will no longer be able to observe the {@link WorkStatus} of the pruned work.
+     * You do not normally need to call this method - WorkManager takes care to auto-prune its work
+     * after a sane period of time.  This method also ignores the
+     * {@link OneTimeWorkRequest.Builder#keepResultsForAtLeast(long, TimeUnit)} policy.
+     */
+    public abstract void pruneWork();
+
+    /**
      * Gets a {@link LiveData} of the last time all work was cancelled.  This method is intended for
      * use by library and module developers who have dependent data in their own repository that
      * must be updated or deleted in case someone cancels their work without their prior knowledge.
diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
index c1eae64..cfb72b8 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
@@ -121,6 +121,7 @@
         public Builder(@NonNull Class<? extends Worker> workerClass) {
             mId = UUID.randomUUID();
             mWorkSpec = new WorkSpec(mId.toString(), workerClass.getName());
+            addTag(workerClass.getName());
         }
 
         /**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Processor.java b/work/workmanager/src/main/java/androidx/work/impl/Processor.java
index 4092686..86b7edf 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Processor.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Processor.java
@@ -20,6 +20,8 @@
 import android.support.annotation.RestrictTo;
 import android.util.Log;
 
+import androidx.work.Configuration;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -38,8 +40,8 @@
     private static final String TAG = "Processor";
 
     private Context mAppContext;
+    private Configuration mConfiguration;
     private WorkDatabase mWorkDatabase;
-
     private Map<String, WorkerWrapper> mEnqueuedWorkMap;
     private List<Scheduler> mSchedulers;
     private Executor mExecutor;
@@ -50,10 +52,12 @@
 
     public Processor(
             Context appContext,
+            Configuration configuration,
             WorkDatabase workDatabase,
             List<Scheduler> schedulers,
             Executor executor) {
         mAppContext = appContext;
+        mConfiguration = configuration;
         mWorkDatabase = workDatabase;
         mEnqueuedWorkMap = new HashMap<>();
         mSchedulers = schedulers;
@@ -87,11 +91,12 @@
             return false;
         }
 
-        WorkerWrapper workWrapper = new WorkerWrapper.Builder(mAppContext, mWorkDatabase, id)
-                .withListener(this)
-                .withSchedulers(mSchedulers)
-                .withRuntimeExtras(runtimeExtras)
-                .build();
+        WorkerWrapper workWrapper =
+                new WorkerWrapper.Builder(mAppContext, mConfiguration, mWorkDatabase, id)
+                        .withListener(this)
+                        .withSchedulers(mSchedulers)
+                        .withRuntimeExtras(runtimeExtras)
+                        .build();
         mEnqueuedWorkMap.put(id, workWrapper);
         mExecutor.execute(workWrapper);
         Log.d(TAG, String.format("%s: processing %s", getClass().getSimpleName(), id));
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
index c6e5b81..4944549 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -25,6 +25,7 @@
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
+import androidx.work.Configuration;
 import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
 import androidx.work.impl.background.systemalarm.SystemAlarmService;
 import androidx.work.impl.background.systemjob.SystemJobScheduler;
@@ -61,11 +62,14 @@
      * @param schedulers   The {@link List} of {@link Scheduler}s to delegate to.
      */
     public static void schedule(
+            @NonNull Configuration configuration,
             @NonNull WorkDatabase workDatabase,
             List<Scheduler> schedulers) {
 
         WorkSpecDao workSpecDao = workDatabase.workSpecDao();
-        List<WorkSpec> eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling();
+        List<WorkSpec> eligibleWorkSpecs =
+                workSpecDao.getEligibleWorkForScheduling(
+                        configuration.getMaxSchedulerLimit());
         scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs);
     }
 
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
index 0c3e3db..82a26ac 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
@@ -38,7 +38,7 @@
     private static final int VERSION_2 = 2;
 
     private static final String CREATE_SYSTEM_ID_INFO =
-            "CREATE TABLE IF NOT EXISTS `systemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`"
+            "CREATE TABLE IF NOT EXISTS `SystemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`"
                     + " INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`), FOREIGN KEY(`work_spec_id`)"
                     + " REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )";
 
@@ -49,20 +49,20 @@
                     + "CASCADE )";
 
     private static final String MIGRATE_ALARM_INFO_TO_SYSTEM_ID_INFO =
-            "INSERT INTO systemIdInfo(work_spec_id, system_id) "
+            "INSERT INTO SystemIdInfo(work_spec_id, system_id) "
                     + "SELECT work_spec_id, alarm_id AS system_id FROM alarmInfo";
 
     private static final String MIGRATE_SYSTEM_ID_INFO_TO_ALARM_INFO =
             "INSERT INTO alarmInfo(work_spec_id, alarm_id) "
-                    + "SELECT work_spec_id, system_id AS alarm_id FROM systemIdInfo";
+                    + "SELECT work_spec_id, system_id AS alarm_id FROM SystemIdInfo";
 
     private static final String REMOVE_ALARM_INFO = "DROP TABLE IF EXISTS alarmInfo";
-    private static final String REMOVE_SYSTEM_ID_INFO = "DROP TABLE IF EXISTS systemIdInfo";
-
+    private static final String REMOVE_SYSTEM_ID_INFO = "DROP TABLE IF EXISTS SystemIdInfo";
 
     /**
      * Removes the {@code alarmInfo} table and substitutes it for a more general
-     * {@code systemIdInfo} table.
+     * {@code SystemIdInfo} table.
+     * Adds implicit work tags for all work (a tag with the worker class name).
      */
     public static Migration MIGRATION_1_2 = new Migration(VERSION_1, VERSION_2) {
         @Override
@@ -70,12 +70,14 @@
             database.execSQL(CREATE_SYSTEM_ID_INFO);
             database.execSQL(MIGRATE_ALARM_INFO_TO_SYSTEM_ID_INFO);
             database.execSQL(REMOVE_ALARM_INFO);
+            database.execSQL("INSERT INTO worktag(tag, work_spec_id) "
+                    + "SELECT worker_class_name AS tag, id AS work_spec_id FROM workspec");
         }
     };
 
     /**
      * Removes the {@code alarmInfo} table and substitutes it for a more general
-     * {@code systemIdInfo} table.
+     * {@code SystemIdInfo} table.
      */
     public static Migration MIGRATION_2_1 = new Migration(VERSION_2, VERSION_1) {
         @Override
@@ -83,6 +85,7 @@
             database.execSQL(CREATE_ALARM_INFO);
             database.execSQL(MIGRATE_SYSTEM_ID_INFO_TO_ALARM_INFO);
             database.execSQL(REMOVE_SYSTEM_ID_INFO);
+            // Don't remove implicit tags; they may have been added by the developer.
         }
     };
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 119325c..0dcdda3 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -43,6 +43,7 @@
 import androidx.work.impl.utils.ForceStopRunnable;
 import androidx.work.impl.utils.LiveDataUtils;
 import androidx.work.impl.utils.Preferences;
+import androidx.work.impl.utils.PruneWorkRunnable;
 import androidx.work.impl.utils.StartWorkRunnable;
 import androidx.work.impl.utils.StopWorkRunnable;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -166,6 +167,7 @@
         mTaskExecutor = WorkManagerTaskExecutor.getInstance();
         mProcessor = new Processor(
                 context,
+                mConfiguration,
                 mWorkDatabase,
                 getSchedulers(),
                 configuration.getExecutor());
@@ -349,6 +351,7 @@
     }
 
     @Override
+    @WorkerThread
     public void cancelUniqueWorkSync(@NonNull String uniqueWorkName) {
         assertBackgroundThread("Cannot cancelAllWorkByNameBlocking on main thread!");
         CancelWorkRunnable.forName(uniqueWorkName, this).run();
@@ -360,6 +363,7 @@
     }
 
     @Override
+    @WorkerThread
     public void cancelAllWorkSync() {
         assertBackgroundThread("Cannot cancelAllWorkSync on main thread!");
         CancelWorkRunnable.forAll(this).run();
@@ -376,6 +380,18 @@
     }
 
     @Override
+    public void pruneWork() {
+        mTaskExecutor.executeOnBackgroundThread(new PruneWorkRunnable(this));
+    }
+
+    @Override
+    @WorkerThread
+    public void pruneWorkSync() {
+        assertBackgroundThread("Cannot pruneWork on main thread!");
+        new PruneWorkRunnable(this).run();
+    }
+
+    @Override
     public LiveData<WorkStatus> getStatusById(@NonNull UUID id) {
         WorkSpecDao dao = mWorkDatabase.workSpecDao();
         LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
@@ -500,7 +516,7 @@
         // Delegate to the WorkManager's schedulers.
         // Using getters here so we can use from a mocked instance
         // of WorkManagerImpl.
-        Schedulers.schedule(getWorkDatabase(), getSchedulers());
+        Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers());
     }
 
     private void assertBackgroundThread(String errorMessage) {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index b6d4fc1..0b8c44c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -30,6 +30,7 @@
 import android.support.annotation.WorkerThread;
 import android.util.Log;
 
+import androidx.work.Configuration;
 import androidx.work.Data;
 import androidx.work.InputMerger;
 import androidx.work.State;
@@ -63,6 +64,7 @@
     private WorkSpec mWorkSpec;
     Worker mWorker;
 
+    private Configuration mConfiguration;
     private WorkDatabase mWorkDatabase;
     private WorkSpecDao mWorkSpecDao;
     private DependencyDao mDependencyDao;
@@ -78,6 +80,7 @@
         mRuntimeExtras = builder.mRuntimeExtras;
         mWorker = builder.mWorker;
 
+        mConfiguration = builder.mConfiguration;
         mWorkDatabase = builder.mWorkDatabase;
         mWorkSpecDao = mWorkDatabase.workSpecDao();
         mDependencyDao = mWorkDatabase.dependencyDao();
@@ -300,7 +303,7 @@
             notifyListener(false, false);
         }
 
-        Schedulers.schedule(mWorkDatabase, mSchedulers);
+        Schedulers.schedule(mConfiguration, mWorkDatabase, mSchedulers);
     }
 
     private void recursivelyFailWorkAndDependents(String workSpecId) {
@@ -370,7 +373,7 @@
         }
 
         // This takes of scheduling the dependent workers as they have been marked ENQUEUED.
-        Schedulers.schedule(mWorkDatabase, mSchedulers);
+        Schedulers.schedule(mConfiguration, mWorkDatabase, mSchedulers);
     }
 
     static Worker workerFromWorkSpec(@NonNull Context context,
@@ -434,6 +437,7 @@
         private Context mAppContext;
         @Nullable
         private Worker mWorker;
+        private Configuration mConfiguration;
         private WorkDatabase mWorkDatabase;
         private String mWorkSpecId;
         private ExecutionListener mListener;
@@ -441,9 +445,11 @@
         private Extras.RuntimeExtras mRuntimeExtras;
 
         public Builder(@NonNull Context context,
+                @NonNull Configuration configuration,
                 @NonNull WorkDatabase database,
                 @NonNull String workSpecId) {
             mAppContext = context.getApplicationContext();
+            mConfiguration = configuration;
             mWorkDatabase = database;
             mWorkSpecId = workSpecId;
         }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
index 94cc2db..6dbd752 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
@@ -60,9 +60,14 @@
 
     @WorkerThread
     void handleConstraintsChanged() {
+        int schedulerLimit = mDispatcher
+                .getWorkManager()
+                .getConfiguration()
+                .getMaxSchedulerLimit();
+
         List<WorkSpec> candidates = mDispatcher.getWorkManager().getWorkDatabase()
                 .workSpecDao()
-                .getEligibleWorkForScheduling();
+                .getEligibleWorkForScheduling(schedulerLimit);
 
         // Filter candidates that are marked as SCHEDULE_NOT_REQUESTED_AT
         List<WorkSpec> eligibleWorkSpecs = new ArrayList<>(candidates.size());
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
index e869762..cd97958 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
@@ -28,8 +28,7 @@
  *
  * @hide
  */
-@Entity(tableName = "systemIdInfo",
-        foreignKeys = {
+@Entity(foreignKeys = {
                 @ForeignKey(
                         entity = WorkSpec.class,
                         parentColumns = "id",
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
index bcee05b..383e516 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
@@ -42,7 +42,7 @@
      * @return The instance of {@link SystemIdInfo} if exists.
      */
     @Nullable
-    @Query("SELECT * FROM systemIdInfo WHERE work_spec_id=:workSpecId")
+    @Query("SELECT * FROM SystemIdInfo WHERE work_spec_id=:workSpecId")
     SystemIdInfo getSystemIdInfo(@NonNull String workSpecId);
 
     /**
@@ -50,6 +50,6 @@
      *
      * @param workSpecId The {@link WorkSpec} identifier.
      */
-    @Query("DELETE FROM systemIdInfo where work_spec_id=:workSpecId")
+    @Query("DELETE FROM SystemIdInfo where work_spec_id=:workSpecId")
     void removeSystemIdInfo(@NonNull String workSpecId);
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
index 7d9775d..e15124f 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
@@ -29,7 +29,6 @@
 
 import androidx.work.Data;
 import androidx.work.State;
-import androidx.work.impl.Scheduler;
 
 import java.util.List;
 
@@ -285,10 +284,23 @@
             // We only want WorkSpecs which have not been previously scheduled.
             + " AND schedule_requested_at=" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET
             + " LIMIT "
-                + "(SELECT " + Scheduler.MAX_SCHEDULER_LIMIT + "-COUNT(*) FROM workspec WHERE"
+                + "(SELECT :schedulerLimit" + "-COUNT(*) FROM workspec WHERE"
                     + " schedule_requested_at<>" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET
                     + " AND state NOT IN " + COMPLETED_STATES
                 + ")"
     )
-    List<WorkSpec> getEligibleWorkForScheduling();
+    List<WorkSpec> getEligibleWorkForScheduling(int schedulerLimit);
+
+    /**
+     * Immediately prunes eligible work from the database meeting the following criteria:
+     * - Is finished (succeeded, failed, or cancelled)
+     * - Has zero unfinished dependents
+     */
+    @Query("DELETE FROM workspec WHERE "
+            + "state IN " + COMPLETED_STATES
+            + " AND (SELECT COUNT(*)=0 FROM dependency WHERE "
+            + "    prerequisite_id=id AND "
+            + "    work_spec_id NOT IN "
+            + "        (SELECT id FROM workspec WHERE state IN " + COMPLETED_STATES + "))")
+    void pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast();
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
index e117c95..842fdc2 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
@@ -56,7 +56,10 @@
     }
 
     void reschedulePendingWorkers(WorkManagerImpl workManagerImpl) {
-        Schedulers.schedule(workManagerImpl.getWorkDatabase(), workManagerImpl.getSchedulers());
+        Schedulers.schedule(
+                workManagerImpl.getConfiguration(),
+                workManagerImpl.getWorkDatabase(),
+                workManagerImpl.getSchedulers());
     }
 
     private void recursivelyCancelWorkAndDependents(WorkDatabase workDatabase, String workSpecId) {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
index 004b895..00d7b8f 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/EnqueueRunnable.java
@@ -106,7 +106,10 @@
     @VisibleForTesting
     public void scheduleWorkInBackground() {
         WorkManagerImpl workManager = mWorkContinuation.getWorkManagerImpl();
-        Schedulers.schedule(workManager.getWorkDatabase(), workManager.getSchedulers());
+        Schedulers.schedule(
+                workManager.getConfiguration(),
+                workManager.getWorkDatabase(),
+                workManager.getSchedulers());
     }
 
     private static boolean processContinuation(@NonNull WorkContinuationImpl workContinuation) {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
new file mode 100644
index 0000000..bf93f46
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/PruneWorkRunnable.java
@@ -0,0 +1,47 @@
+/*
+ * 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 androidx.work.impl.utils;
+
+import android.support.annotation.RestrictTo;
+
+import androidx.work.impl.WorkDatabase;
+import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.WorkSpecDao;
+
+/**
+ * A Runnable that prunes work in the background.  Pruned work meets the following criteria:
+ * - Is finished (succeeded, failed, or cancelled)
+ * - Has zero unfinished dependents
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class PruneWorkRunnable implements Runnable {
+
+    private WorkManagerImpl mWorkManagerImpl;
+
+    public PruneWorkRunnable(WorkManagerImpl workManagerImpl) {
+        mWorkManagerImpl = workManagerImpl;
+    }
+
+    @Override
+    public void run() {
+        WorkDatabase workDatabase = mWorkManagerImpl.getWorkDatabase();
+        WorkSpecDao workSpecDao = workDatabase.workSpecDao();
+        workSpecDao.pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast();
+    }
+}
diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json
index 00bc68d..400e545 100644
--- a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json
+++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json
@@ -2,7 +2,7 @@
   "formatVersion": 1,
   "database": {
     "version": 2,
-    "identityHash": "244d2ac5ecd0a7fb47b3755737585d7b",
+    "identityHash": "c45e5fcbdf3824dead9778f19e2fd8af",
     "entities": [
       {
         "tableName": "Dependency",
@@ -269,7 +269,7 @@
         ]
       },
       {
-        "tableName": "systemIdInfo",
+        "tableName": "SystemIdInfo",
         "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`work_spec_id` TEXT NOT NULL, `system_id` INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`), FOREIGN KEY(`work_spec_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
         "fields": [
           {
@@ -357,7 +357,7 @@
     ],
     "setupQueries": [
       "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
-      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"244d2ac5ecd0a7fb47b3755737585d7b\")"
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"c45e5fcbdf3824dead9778f19e2fd8af\")"
     ]
   }
 }
\ No newline at end of file