Correctly reschedule periodic work on API < 23.

- Fixes an issue where periodic workers are not correctly rescheduled.
- Enables ForceStopRunnable to reschedule workers on initialization
  so that Workers are in a good state post upgrade to alpha04.
- Use migrations to help determine if we need to reschedule.

Test: Updated unit tests.
Fixes: b/110698000
Change-Id: Icb47403ef2aee32effefa88cceb3c7d0435bdc9f
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index 15bf224..4e62ced 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -25,6 +25,7 @@
 import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
 import android.arch.persistence.room.testing.MigrationTestHelper;
 import android.content.ContentValues;
+import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.support.test.InstrumentationRegistry;
@@ -35,6 +36,7 @@
 import androidx.work.impl.WorkDatabaseMigrations;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkTypeConverters;
+import androidx.work.impl.utils.Preferences;
 import androidx.work.worker.TestWorker;
 
 import org.junit.Before;
@@ -51,8 +53,9 @@
 
     private static final String TEST_DATABASE = "workdatabase-test";
     private static final boolean VALIDATE_DROPPED_TABLES = true;
-    private static final int OLD_VERSION = 1;
-    private static final int NEW_VERSION = 2;
+    private static final int VERSION_1 = 1;
+    private static final int VERSION_2 = 2;
+    private static final int VERSION_3 = 3;
     private static final String COLUMN_WORKSPEC_ID = "work_spec_id";
     private static final String COLUMN_SYSTEM_ID = "system_id";
     private static final String COLUMN_ALARM_ID = "alarm_id";
@@ -69,6 +72,7 @@
     private static final String TABLE_WORKTAG = "WorkTag";
     private static final String TABLE_WORKNAME = "WorkName";
 
+    private Context mContext;
     private File mDatabasePath;
 
     @Rule
@@ -80,6 +84,7 @@
     @Before
     public void setUp() {
         // Delete the database if it exists.
+        mContext = InstrumentationRegistry.getTargetContext();
         mDatabasePath = InstrumentationRegistry.getContext().getDatabasePath(TEST_DATABASE);
         if (mDatabasePath.exists()) {
             mDatabasePath.delete();
@@ -90,7 +95,7 @@
     @MediumTest
     public void testMigrationVersion1To2() throws IOException {
         SupportSQLiteDatabase database =
-                mMigrationTestHelper.createDatabase(TEST_DATABASE, OLD_VERSION);
+                mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_1);
 
         String[] prepopulatedWorkSpecIds = new String[] {
                 UUID.randomUUID().toString(),
@@ -142,7 +147,7 @@
 
         database = mMigrationTestHelper.runMigrationsAndValidate(
                 TEST_DATABASE,
-                NEW_VERSION,
+                VERSION_2,
                 VALIDATE_DROPPED_TABLES,
                 WorkDatabaseMigrations.MIGRATION_1_2);
 
@@ -183,6 +188,25 @@
         database.close();
     }
 
+    @Test
+    @MediumTest
+    public void testMigrationVersion2To3() throws IOException {
+        SupportSQLiteDatabase database =
+                mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_2);
+        WorkDatabaseMigrations.Migration2To3 migration2To3 = new WorkDatabaseMigrations
+                .Migration2To3(mContext);
+
+        database = mMigrationTestHelper.runMigrationsAndValidate(
+                TEST_DATABASE,
+                VERSION_3,
+                VALIDATE_DROPPED_TABLES,
+                migration2To3);
+
+        Preferences preferences = new Preferences(mContext);
+        assertThat(preferences.needsReschedule(), is(true));
+        database.close();
+    }
+
     private boolean checkExists(SupportSQLiteDatabase database, String tableName) {
         Cursor cursor = null;
         try {
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 3e01164..ba9fe04 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -1405,6 +1405,7 @@
         WorkDatabase.generateCleanupCallback().onOpen(db);
 
         assertThat(workSpecDao.getState(work.getStringId()), is(ENQUEUED));
+        assertThat(work.getWorkSpec().scheduleRequestedAt, is(WorkSpec.SCHEDULE_NOT_REQUESTED_YET));
     }
 
     @Test
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 739fcf7..3dae0f9 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -31,6 +31,7 @@
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.greaterThan;
+import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -38,6 +39,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.os.Build;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.LargeTest;
 import android.support.test.filters.SmallTest;
@@ -507,6 +509,7 @@
         insertWork(periodicWork);
         new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId)
                 .withListener(mMockListener)
+                .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build()
                 .run();
 
@@ -514,6 +517,16 @@
         verify(mMockListener).onExecuted(periodicWorkId, true, false);
         assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
         assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
+
+        // SystemAlarmScheduler needs to reschedule the same worker.
+        if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL) {
+            ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
+            verify(mMockScheduler, atLeast(1))
+                    .schedule(captor.capture());
+
+            WorkSpec workSpec = captor.getValue();
+            assertThat(workSpec.id, is(periodicWorkId));
+        }
     }
 
     @Test
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 cfe2cef..2944087 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
@@ -389,6 +389,7 @@
         // Use a mocked scheduler in this test.
         Scheduler scheduler = mock(Scheduler.class);
         doCallRealMethod().when(mWorkManager).rescheduleEligibleWork();
+        when(mWorkManager.getApplicationContext()).thenReturn(mContext);
         when(mWorkManager.getSchedulers()).thenReturn(Collections.singletonList(scheduler));
 
         OneTimeWorkRequest failed = new OneTimeWorkRequest.Builder(TestWorker.class)
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
index fec3878..c706e40 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
@@ -16,11 +16,8 @@
 
 package androidx.work.impl.utils;
 
-import static android.content.Context.MODE_PRIVATE;
-
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -30,9 +27,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -48,8 +43,6 @@
 @RunWith(AndroidJUnit4.class)
 public class ForceStopRunnableTest {
 
-    private static final String PREFERENCES_FILE_NAME = "androidx.work.util.preferences.test";
-
     private Context mContext;
     private WorkManagerImpl mWorkManager;
     private WorkDatabase mWorkDatabase;
@@ -82,7 +75,7 @@
     @Test
     public void testReschedulesOnForceStop() {
         ForceStopRunnable runnable = spy(mRunnable);
-        when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
+        when(runnable.shouldRescheduleWorkers()).thenReturn(false);
         when(runnable.isForceStopped()).thenReturn(true);
         runnable.run();
         verify(mWorkManager, times(1)).rescheduleEligibleWork();
@@ -91,45 +84,17 @@
     @Test
     public void test_doNothingWhenNotForceStopped() {
         ForceStopRunnable runnable = spy(mRunnable);
-        when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
+        when(runnable.shouldRescheduleWorkers()).thenReturn(false);
         when(runnable.isForceStopped()).thenReturn(false);
         runnable.run();
         verify(mWorkManager, times(0)).rescheduleEligibleWork();
     }
 
     @Test
-    public void test_cancelAllJobSchedulerJobs() {
+    public void test_rescheduleWorkers_updatesSharedPreferences() {
         ForceStopRunnable runnable = spy(mRunnable);
-        doNothing().when(runnable).cancelAllInJobScheduler();
-        when(runnable.shouldCancelPersistedJobs()).thenReturn(true);
+        when(runnable.shouldRescheduleWorkers()).thenReturn(true);
         runnable.run();
-        verify(runnable, times(1)).cancelAllInJobScheduler();
-        verify(mPreferences, times(1)).setMigratedPersistedJobs();
-    }
-
-    @Test
-    public void test_doNothingWhenThereIsNothingToCancel() {
-        ForceStopRunnable runnable = spy(mRunnable);
-        doNothing().when(runnable).cancelAllInJobScheduler();
-        when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
-        runnable.run();
-        verify(runnable, times(0)).cancelAllInJobScheduler();
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
-    public void testMigratePersistedJobs() {
-        SharedPreferences testSharedPreferences =
-                mContext.getSharedPreferences(PREFERENCES_FILE_NAME, MODE_PRIVATE);
-        testSharedPreferences.edit()
-                .clear()
-                .apply();
-        Preferences testPreferences = new Preferences(testSharedPreferences);
-        when(mWorkManager.getPreferences()).thenReturn(testPreferences);
-        ForceStopRunnable runnable = spy(mRunnable);
-        doNothing().when(runnable).cancelAllInJobScheduler();
-        assertThat(runnable.shouldCancelPersistedJobs(), is(true));
-        runnable.run();
-        assertThat(runnable.shouldCancelPersistedJobs(), is(false));
+        verify(mPreferences, times(1)).setNeedsReschedule(false);
     }
 }
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 4944549..a31bf46 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -73,6 +73,8 @@
         scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs);
     }
 
+
+
     private static void scheduleInternal(
             @NonNull WorkDatabase workDatabase,
             List<Scheduler> schedulers,
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
index 6a1cc1e..e918906 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -56,12 +56,14 @@
         WorkTag.class,
         SystemIdInfo.class,
         WorkName.class},
-        version = 2)
+        version = 3)
 @TypeConverters(value = {Data.class, WorkTypeConverters.class})
 public abstract class WorkDatabase extends RoomDatabase {
 
     private static final String DB_NAME = "androidx.work.workdb";
-    private static final String CLEANUP_SQL = "UPDATE workspec SET state=" + ENQUEUED
+    private static final String CLEANUP_SQL = "UPDATE workspec "
+            + "SET state=" + ENQUEUED + ","
+            + " schedule_requested_at=" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET
             + " WHERE state=" + RUNNING;
 
     // Delete rows in the workspec table that...
@@ -97,6 +99,7 @@
         }
         return builder.addCallback(generateCleanupCallback())
                 .addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
+                .addMigrations(new WorkDatabaseMigrations.Migration2To3(context))
                 .fallbackToDestructiveMigration()
                 .build();
     }
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 935561b..86d503d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
@@ -18,9 +18,12 @@
 
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.room.migration.Migration;
+import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 
+import androidx.work.impl.utils.Preferences;
+
 /**
  * Migration helpers for {@link androidx.work.impl.WorkDatabase}.
  *
@@ -36,6 +39,7 @@
     // Known WorkDatabase versions
     private static final int VERSION_1 = 1;
     private static final int VERSION_2 = 2;
+    private static final int VERSION_3 = 3;
 
     private static final String CREATE_SYSTEM_ID_INFO =
             "CREATE TABLE IF NOT EXISTS `SystemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`"
@@ -63,4 +67,22 @@
                     + "SELECT worker_class_name AS tag, id AS work_spec_id FROM workspec");
         }
     };
+
+    /**
+     * Migrates {@link WorkDatabase} version 2 to 3.
+     */
+    public static class Migration2To3 extends Migration {
+        final Context mContext;
+
+        public Migration2To3(@NonNull Context context) {
+            super(VERSION_2, VERSION_3);
+            mContext = context;
+        }
+
+        @Override
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
+            Preferences preferences = new Preferences(mContext);
+            preferences.setNeedsReschedule(true);
+        }
+    }
 }
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 88d458a..6e97b53 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -19,6 +19,7 @@
 import android.arch.core.util.Function;
 import android.arch.lifecycle.LiveData;
 import android.content.Context;
+import android.os.Build;
 import android.os.Looper;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -37,6 +38,7 @@
 import androidx.work.WorkRequest;
 import androidx.work.WorkStatus;
 import androidx.work.impl.background.greedy.GreedyScheduler;
+import androidx.work.impl.background.systemjob.SystemJobScheduler;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.CancelWorkRunnable;
@@ -511,6 +513,11 @@
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public void rescheduleEligibleWork() {
+        // TODO (rahulrav@) Make every scheduler do its own cancelAll().
+        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+            SystemJobScheduler.jobSchedulerCancelAll(getApplicationContext());
+        }
+
         // Reset scheduled state.
         getWorkDatabase().workSpecDao().resetScheduledState();
 
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 cd1241b..eed0ca4 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -23,6 +23,7 @@
 import static androidx.work.State.SUCCEEDED;
 
 import android.content.Context;
+import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
@@ -346,11 +347,20 @@
             mWorkSpecDao.setPeriodStartTime(mWorkSpecId, nextPeriodStartTime);
             mWorkSpecDao.setState(ENQUEUED, mWorkSpecId);
             mWorkSpecDao.resetWorkSpecRunAttemptCount(mWorkSpecId);
+            // We need to tell the schedulers that this WorkSpec is no longer occupying a slot.
+            mWorkSpecDao.markWorkSpecScheduled(mWorkSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET);
             mWorkDatabase.setTransactionSuccessful();
         } finally {
             mWorkDatabase.endTransaction();
             notifyListener(isSuccessful, false);
         }
+
+        // We need to tell the Schedulers to pick up this newly ENQUEUED Worker.
+        // TODO (rahulrav@) Move this into the Scheduler itself.
+        if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL) {
+            // Reschedule the periodic work.
+            Schedulers.schedule(mConfiguration, mWorkDatabase, mSchedulers);
+        }
     }
 
     private void setSucceededAndNotify() {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index ab6b067..fbcaa42 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
@@ -20,10 +20,8 @@
 import static android.app.PendingIntent.FLAG_NO_CREATE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 
-import android.annotation.TargetApi;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
-import android.app.job.JobScheduler;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -34,7 +32,6 @@
 import android.util.Log;
 
 import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.background.systemjob.SystemJobScheduler;
 
 import java.util.concurrent.TimeUnit;
 
@@ -67,12 +64,11 @@
 
     @Override
     public void run() {
-        if (shouldCancelPersistedJobs()) {
-            cancelAllInJobScheduler();
-            Log.d(TAG, "Migrating persisted jobs.");
+        if (shouldRescheduleWorkers()) {
+            Log.d(TAG, "Rescheduling Workers.");
             mWorkManager.rescheduleEligibleWork();
             // Mark the jobs as migrated.
-            mWorkManager.getPreferences().setMigratedPersistedJobs();
+            mWorkManager.getPreferences().setNeedsReschedule(false);
         } else if (isForceStopped()) {
             Log.d(TAG, "Application was force-stopped, rescheduling.");
             mWorkManager.rescheduleEligibleWork();
@@ -98,12 +94,11 @@
     }
 
     /**
-     * @return {@code true} If persisted jobs in JobScheduler need to be cancelled.
+     * @return {@code true} If we need to reschedule Workers.
      */
     @VisibleForTesting
-    public boolean shouldCancelPersistedJobs() {
-        return Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
-                && mWorkManager.getPreferences().shouldMigratePersistedJobs();
+    public boolean shouldRescheduleWorkers() {
+        return mWorkManager.getPreferences().needsReschedule();
     }
 
     /**
@@ -128,15 +123,6 @@
         return intent;
     }
 
-    /**
-     * Cancels all the persisted jobs in {@link JobScheduler}.
-     */
-    @VisibleForTesting
-    @TargetApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
-    public void cancelAllInJobScheduler() {
-        SystemJobScheduler.jobSchedulerCancelAll(mContext);
-    }
-
     void setAlarm(int alarmId) {
         AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         // Using FLAG_UPDATE_CURRENT, because we only ever want once instance of this alarm.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
index 529a2b4..d8b9484 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
@@ -37,7 +37,7 @@
     private static final String PREFERENCES_FILE_NAME = "androidx.work.util.preferences";
 
     private static final String KEY_LAST_CANCEL_ALL_TIME_MS = "last_cancel_all_time_ms";
-    private static final String KEY_MIGRATE_PERSISTED_JOBS = "migrate_persisted_jobs";
+    private static final String KEY_RESCHEDULE_NEEDED = "reschedule_needed";
 
     private SharedPreferences mSharedPreferences;
 
@@ -46,7 +46,7 @@
     }
 
     @VisibleForTesting
-    Preferences(@NonNull SharedPreferences preferences) {
+    public Preferences(@NonNull SharedPreferences preferences) {
         mSharedPreferences = preferences;
     }
 
@@ -75,20 +75,19 @@
     }
 
     /**
-     * @return {@code true} When we should migrate from persisted jobs to non-persisted jobs in
-     * {@link android.app.job.JobScheduler}
+     * @return {@code true} When we should reschedule workers.
      */
-    public boolean shouldMigratePersistedJobs() {
+    public boolean needsReschedule() {
+        // This preference is being set by a Room Migration.
         // TODO Remove this before WorkManager 1.0 beta.
-        return mSharedPreferences.getBoolean(KEY_MIGRATE_PERSISTED_JOBS, true);
+        return mSharedPreferences.getBoolean(KEY_RESCHEDULE_NEEDED, false);
     }
 
     /**
-     * Updates the key which indicates that we have migrated all our persisted jobs in
-     * {@link android.app.job.JobScheduler}.
+     * Updates the key which indicates that we have rescheduled jobs.
      */
-    public void setMigratedPersistedJobs() {
-        mSharedPreferences.edit().putBoolean(KEY_MIGRATE_PERSISTED_JOBS, false).apply();
+    public void setNeedsReschedule(boolean needsReschedule) {
+        mSharedPreferences.edit().putBoolean(KEY_RESCHEDULE_NEEDED, needsReschedule).apply();
     }
 
     /**
diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/3.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/3.json
new file mode 100644
index 0000000..69e73eb
--- /dev/null
+++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/3.json
@@ -0,0 +1,363 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 3,
+    "identityHash": "c45e5fcbdf3824dead9778f19e2fd8af",
+    "entities": [
+      {
+        "tableName": "Dependency",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`work_spec_id` TEXT NOT NULL, `prerequisite_id` TEXT NOT NULL, PRIMARY KEY(`work_spec_id`, `prerequisite_id`), FOREIGN KEY(`work_spec_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE , FOREIGN KEY(`prerequisite_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "workSpecId",
+            "columnName": "work_spec_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "prerequisiteId",
+            "columnName": "prerequisite_id",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "work_spec_id",
+            "prerequisite_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Dependency_work_spec_id",
+            "unique": false,
+            "columnNames": [
+              "work_spec_id"
+            ],
+            "createSql": "CREATE  INDEX `index_Dependency_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+          },
+          {
+            "name": "index_Dependency_prerequisite_id",
+            "unique": false,
+            "columnNames": [
+              "prerequisite_id"
+            ],
+            "createSql": "CREATE  INDEX `index_Dependency_prerequisite_id` ON `${TABLE_NAME}` (`prerequisite_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "work_spec_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          },
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "prerequisite_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "WorkSpec",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `state` INTEGER NOT NULL, `worker_class_name` TEXT NOT NULL, `input_merger_class_name` TEXT, `input` BLOB NOT NULL, `output` BLOB NOT NULL, `initial_delay` INTEGER NOT NULL, `interval_duration` INTEGER NOT NULL, `flex_duration` INTEGER NOT NULL, `run_attempt_count` INTEGER NOT NULL, `backoff_policy` INTEGER NOT NULL, `backoff_delay_duration` INTEGER NOT NULL, `period_start_time` INTEGER NOT NULL, `minimum_retention_duration` INTEGER NOT NULL, `schedule_requested_at` INTEGER NOT NULL, `required_network_type` INTEGER, `requires_charging` INTEGER NOT NULL, `requires_device_idle` INTEGER NOT NULL, `requires_battery_not_low` INTEGER NOT NULL, `requires_storage_not_low` INTEGER NOT NULL, `content_uri_triggers` BLOB, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "state",
+            "columnName": "state",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "workerClassName",
+            "columnName": "worker_class_name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "inputMergerClassName",
+            "columnName": "input_merger_class_name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "input",
+            "columnName": "input",
+            "affinity": "BLOB",
+            "notNull": true
+          },
+          {
+            "fieldPath": "output",
+            "columnName": "output",
+            "affinity": "BLOB",
+            "notNull": true
+          },
+          {
+            "fieldPath": "initialDelay",
+            "columnName": "initial_delay",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "intervalDuration",
+            "columnName": "interval_duration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "flexDuration",
+            "columnName": "flex_duration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "runAttemptCount",
+            "columnName": "run_attempt_count",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "backoffPolicy",
+            "columnName": "backoff_policy",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "backoffDelayDuration",
+            "columnName": "backoff_delay_duration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "periodStartTime",
+            "columnName": "period_start_time",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "minimumRetentionDuration",
+            "columnName": "minimum_retention_duration",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "scheduleRequestedAt",
+            "columnName": "schedule_requested_at",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.mRequiredNetworkType",
+            "columnName": "required_network_type",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "constraints.mRequiresCharging",
+            "columnName": "requires_charging",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.mRequiresDeviceIdle",
+            "columnName": "requires_device_idle",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.mRequiresBatteryNotLow",
+            "columnName": "requires_battery_not_low",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.mRequiresStorageNotLow",
+            "columnName": "requires_storage_not_low",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.mContentUriTriggers",
+            "columnName": "content_uri_triggers",
+            "affinity": "BLOB",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_WorkSpec_schedule_requested_at",
+            "unique": false,
+            "columnNames": [
+              "schedule_requested_at"
+            ],
+            "createSql": "CREATE  INDEX `index_WorkSpec_schedule_requested_at` ON `${TABLE_NAME}` (`schedule_requested_at`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "WorkTag",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`tag` TEXT NOT NULL, `work_spec_id` TEXT NOT NULL, PRIMARY KEY(`tag`, `work_spec_id`), FOREIGN KEY(`work_spec_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "tag",
+            "columnName": "tag",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "workSpecId",
+            "columnName": "work_spec_id",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "tag",
+            "work_spec_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_WorkTag_work_spec_id",
+            "unique": false,
+            "columnNames": [
+              "work_spec_id"
+            ],
+            "createSql": "CREATE  INDEX `index_WorkTag_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "work_spec_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "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": [
+          {
+            "fieldPath": "workSpecId",
+            "columnName": "work_spec_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "systemId",
+            "columnName": "system_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "work_spec_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "work_spec_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "WorkName",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `work_spec_id` TEXT NOT NULL, PRIMARY KEY(`name`, `work_spec_id`), FOREIGN KEY(`work_spec_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "workSpecId",
+            "columnName": "work_spec_id",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "name",
+            "work_spec_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_WorkName_work_spec_id",
+            "unique": false,
+            "columnNames": [
+              "work_spec_id"
+            ],
+            "createSql": "CREATE  INDEX `index_WorkName_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "work_spec_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      }
+    ],
+    "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, \"c45e5fcbdf3824dead9778f19e2fd8af\")"
+    ]
+  }
+}
\ No newline at end of file