Merge changes from topic "periodic-work-requests" into pi-preview1-androidx-dev
am: ca2810524b

Change-Id: I79ad375291edf687522e48c6502d583ee8a211af
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index 4e62ced..0cba3cf 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -18,6 +18,12 @@
 
 import static android.database.sqlite.SQLiteDatabase.CONFLICT_FAIL;
 
+import static androidx.work.impl.WorkDatabaseMigrations.MIGRATION_3_4;
+import static androidx.work.impl.WorkDatabaseMigrations.VERSION_1;
+import static androidx.work.impl.WorkDatabaseMigrations.VERSION_2;
+import static androidx.work.impl.WorkDatabaseMigrations.VERSION_3;
+import static androidx.work.impl.WorkDatabaseMigrations.VERSION_4;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
@@ -28,12 +34,15 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
+import android.os.Build;
+import android.support.annotation.NonNull;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkDatabaseMigrations;
+import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkTypeConverters;
 import androidx.work.impl.utils.Preferences;
@@ -53,9 +62,6 @@
 
     private static final String TEST_DATABASE = "workdatabase-test";
     private static final boolean VALIDATE_DROPPED_TABLES = true;
-    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";
@@ -97,35 +103,12 @@
         SupportSQLiteDatabase database =
                 mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_1);
 
-        String[] prepopulatedWorkSpecIds = new String[] {
+        String[] prepopulatedWorkSpecIds = new String[]{
                 UUID.randomUUID().toString(),
                 UUID.randomUUID().toString()
         };
         for (String workSpecId : prepopulatedWorkSpecIds) {
-            ContentValues contentValues = new ContentValues();
-            contentValues.put("id", workSpecId);
-            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);
+            ContentValues contentValues = contentValues(workSpecId);
             database.insert("workspec", CONFLICT_FAIL, contentValues);
 
             if (workSpecId.equals(prepopulatedWorkSpecIds[0])) {
@@ -193,8 +176,8 @@
     public void testMigrationVersion2To3() throws IOException {
         SupportSQLiteDatabase database =
                 mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_2);
-        WorkDatabaseMigrations.Migration2To3 migration2To3 = new WorkDatabaseMigrations
-                .Migration2To3(mContext);
+        WorkDatabaseMigrations.WorkMigration migration2To3 =
+                new WorkDatabaseMigrations.WorkMigration(mContext, VERSION_2, VERSION_3);
 
         database = mMigrationTestHelper.runMigrationsAndValidate(
                 TEST_DATABASE,
@@ -207,6 +190,79 @@
         database.close();
     }
 
+    @Test
+    @MediumTest
+    public void testMigrationVersion3To4() throws IOException {
+        SupportSQLiteDatabase database =
+                mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_3);
+
+        String oneTimeWorkSpecId = UUID.randomUUID().toString();
+        long scheduleRequestedAt = System.currentTimeMillis();
+        ContentValues oneTimeWorkSpecContentValues = contentValues(oneTimeWorkSpecId);
+        oneTimeWorkSpecContentValues.put("schedule_requested_at", scheduleRequestedAt);
+
+        String periodicWorkSpecId = UUID.randomUUID().toString();
+        ContentValues periodicWorkSpecContentValues = contentValues(periodicWorkSpecId);
+        periodicWorkSpecContentValues.put("interval_duration", 15 * 60 * 1000L);
+
+        database.insert("workspec", CONFLICT_FAIL, oneTimeWorkSpecContentValues);
+        database.insert("workspec", CONFLICT_FAIL, periodicWorkSpecContentValues);
+
+        database = mMigrationTestHelper.runMigrationsAndValidate(
+                TEST_DATABASE,
+                VERSION_4,
+                VALIDATE_DROPPED_TABLES,
+                MIGRATION_3_4);
+
+        Cursor cursor = database.query("SELECT * from workspec");
+        assertThat(cursor.getCount(), is(2));
+        cursor.moveToFirst();
+        assertThat(cursor.getString(cursor.getColumnIndex("id")),
+                is(oneTimeWorkSpecId));
+        assertThat(cursor.getLong(cursor.getColumnIndex("schedule_requested_at")),
+                is(scheduleRequestedAt));
+        cursor.moveToNext();
+        assertThat(cursor.getString(cursor.getColumnIndex("id")),
+                is(periodicWorkSpecId));
+        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+            assertThat(cursor.getLong(cursor.getColumnIndex("schedule_requested_at")),
+                    is(0L));
+        } else {
+            assertThat(cursor.getLong(cursor.getColumnIndex("schedule_requested_at")),
+                    is(WorkSpec.SCHEDULE_NOT_REQUESTED_YET));
+        }
+        database.close();
+    }
+
+    @NonNull
+    private ContentValues contentValues(String workSpecId) {
+        ContentValues contentValues = new ContentValues();
+        contentValues.put("id", workSpecId);
+        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);
+        return contentValues;
+    }
+
     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 8e54d07..4e1dc8f 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -27,6 +27,7 @@
 import static androidx.work.State.FAILED;
 import static androidx.work.State.RUNNING;
 import static androidx.work.State.SUCCEEDED;
+import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -1405,7 +1406,7 @@
         WorkDatabase.generateCleanupCallback().onOpen(db);
 
         assertThat(workSpecDao.getState(work.getStringId()), is(ENQUEUED));
-        assertThat(work.getWorkSpec().scheduleRequestedAt, is(WorkSpec.SCHEDULE_NOT_REQUESTED_YET));
+        assertThat(work.getWorkSpec().scheduleRequestedAt, is(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 c9a8e64..44e65f8 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -517,7 +517,6 @@
         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);
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 bd6ca33..b8bc72c 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -16,6 +16,9 @@
 
 package androidx.work.impl;
 
+import static androidx.work.impl.WorkDatabaseMigrations.MIGRATION_3_4;
+import static androidx.work.impl.WorkDatabaseMigrations.VERSION_2;
+import static androidx.work.impl.WorkDatabaseMigrations.VERSION_3;
 import static androidx.work.impl.model.WorkTypeConverters.StateIds.COMPLETED_STATES;
 import static androidx.work.impl.model.WorkTypeConverters.StateIds.ENQUEUED;
 import static androidx.work.impl.model.WorkTypeConverters.StateIds.RUNNING;
@@ -56,7 +59,7 @@
         WorkTag.class,
         SystemIdInfo.class,
         WorkName.class},
-        version = 3)
+        version = 4)
 @TypeConverters(value = {Data.class, WorkTypeConverters.class})
 public abstract class WorkDatabase extends RoomDatabase {
 
@@ -84,7 +87,7 @@
     /**
      * Creates an instance of the WorkDatabase.
      *
-     * @param context A context (this method will use the application context from it)
+     * @param context         A context (this method will use the application context from it)
      * @param useTestDatabase {@code true} to generate an in-memory database that allows main thread
      *                        access
      * @return The created WorkDatabase
@@ -97,9 +100,12 @@
         } else {
             builder = Room.databaseBuilder(context, WorkDatabase.class, DB_NAME);
         }
+
         return builder.addCallback(generateCleanupCallback())
                 .addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
-                .addMigrations(new WorkDatabaseMigrations.Migration2To3(context))
+                .addMigrations(
+                        new WorkDatabaseMigrations.WorkMigration(context, VERSION_2, VERSION_3))
+                .addMigrations(MIGRATION_3_4)
                 .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 86d503d..d75b514 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
@@ -19,9 +19,12 @@
 import android.arch.persistence.db.SupportSQLiteDatabase;
 import android.arch.persistence.room.migration.Migration;
 import android.content.Context;
+import android.os.Build;
 import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
 
+import androidx.work.impl.model.WorkSpec;
+import androidx.work.impl.model.WorkTypeConverters;
 import androidx.work.impl.utils.Preferences;
 
 /**
@@ -37,9 +40,10 @@
     }
 
     // Known WorkDatabase versions
-    private static final int VERSION_1 = 1;
-    private static final int VERSION_2 = 2;
-    private static final int VERSION_3 = 3;
+    public static final int VERSION_1 = 1;
+    public static final int VERSION_2 = 2;
+    public static final int VERSION_3 = 3;
+    public static final int VERSION_4 = 4;
 
     private static final String CREATE_SYSTEM_ID_INFO =
             "CREATE TABLE IF NOT EXISTS `SystemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`"
@@ -50,6 +54,12 @@
             "INSERT INTO SystemIdInfo(work_spec_id, system_id) "
                     + "SELECT work_spec_id, alarm_id AS system_id FROM alarmInfo";
 
+    private static final String PERIODIC_WORK_SET_SCHEDULE_REQUESTED_AT =
+            "UPDATE workspec SET schedule_requested_at=0"
+                    + " WHERE state NOT IN " + WorkTypeConverters.StateIds.COMPLETED_STATES
+                    + " AND schedule_requested_at=" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET
+                    + " AND interval_duration<>0";
+
     private static final String REMOVE_ALARM_INFO = "DROP TABLE IF EXISTS alarmInfo";
 
     /**
@@ -69,13 +79,13 @@
     };
 
     /**
-     * Migrates {@link WorkDatabase} version 2 to 3.
+     * A {@link WorkDatabase} migration that reschedules all eligible Workers.
      */
-    public static class Migration2To3 extends Migration {
+    public static class WorkMigration extends Migration {
         final Context mContext;
 
-        public Migration2To3(@NonNull Context context) {
-            super(VERSION_2, VERSION_3);
+        public WorkMigration(@NonNull Context context, int startVersion, int endVersion) {
+            super(startVersion, endVersion);
             mContext = context;
         }
 
@@ -85,4 +95,17 @@
             preferences.setNeedsReschedule(true);
         }
     }
+
+    /**
+     * Marks {@code SCHEDULE_REQUESTED_AT} to something other than
+     * {@code SCHEDULE_NOT_REQUESTED_AT}.
+     */
+    public static Migration MIGRATION_3_4 = new Migration(VERSION_3, VERSION_4) {
+        @Override
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
+            if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+                database.execSQL(PERIODIC_WORK_SET_SCHEDULE_REQUESTED_AT);
+            }
+        }
+    };
 }
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 ef1214c..87f922f 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -21,6 +21,7 @@
 import static androidx.work.State.FAILED;
 import static androidx.work.State.RUNNING;
 import static androidx.work.State.SUCCEEDED;
+import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
 
 import android.content.Context;
 import android.os.Build;
@@ -355,8 +356,17 @@
             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);
+            if (Build.VERSION.SDK_INT < WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+                // We only need to reset the schedule_requested_at bit for the AlarmManager
+                // implementation because AlarmManager does not know about periodic WorkRequests.
+                // Otherwise we end up double scheduling the Worker with an identical jobId, and
+                // JobScheduler treats it as the first schedule for a PeriodicWorker. With the
+                // AlarmManager implementation, this is not an problem as AlarmManager only cares
+                // about the actual alarm itself.
+
+                // We need to tell the schedulers that this WorkSpec is no longer occupying a slot.
+                mWorkSpecDao.markWorkSpecScheduled(mWorkSpecId, SCHEDULE_NOT_REQUESTED_YET);
+            }
             mWorkDatabase.setTransactionSuccessful();
         } finally {
             mWorkDatabase.endTransaction();
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
index 6f5120d..52d3090 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpec.java
@@ -114,6 +114,14 @@
     @ColumnInfo(name = "minimum_retention_duration")
     public long minimumRetentionDuration;
 
+    /**
+     * This field tells us if this {@link WorkSpec} instance, is actually currently scheduled and
+     * being counted against the {@code SCHEDULER_LIMIT}. This bit is reset for PeriodicWorkRequests
+     * in API < 23, because AlarmManager does not know of PeriodicWorkRequests. So for the next
+     * request to be rescheduled this field has to be reset to {@code SCHEDULE_NOT_REQUESTED_AT}.
+     * For the JobScheduler implementation, we don't reset this field because JobScheduler natively
+     * supports PeriodicWorkRequests.
+     */
     @ColumnInfo(name = "schedule_requested_at")
     public long scheduleRequestedAt = SCHEDULE_NOT_REQUESTED_YET;
 
diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/4.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/4.json
new file mode 100644
index 0000000..63c3005
--- /dev/null
+++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/4.json
@@ -0,0 +1,363 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 4,
+    "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