Merge pi-qpr1-release PQ1A.181105.017.A1 to pi-platform-release

Change-Id: I192eb70ee1f81a4e177a530061ec849044c27d4a
diff --git a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
index 40ed9c4..e74ee0c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/DiffAndDocs.kt
@@ -27,6 +27,7 @@
 import androidx.build.doclava.CHECK_API_CONFIG_RELEASE
 import androidx.build.doclava.CHECK_API_CONFIG_PATCH
 import androidx.build.doclava.ChecksConfig
+import androidx.build.docs.ConcatenateFilesTask
 import androidx.build.docs.GenerateDocsTask
 import androidx.build.jdiff.JDiffTask
 import com.android.build.gradle.AppExtension
@@ -46,6 +47,8 @@
 import org.gradle.api.tasks.compile.JavaCompile
 import org.gradle.api.tasks.javadoc.Javadoc
 import java.io.File
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
 import kotlin.collections.Collection
 import kotlin.collections.List
 import kotlin.collections.MutableMap
@@ -69,7 +72,13 @@
 
     private lateinit var rules: List<PublishDocsRules>
     private val docsTasks: MutableMap<String, GenerateDocsTask> = mutableMapOf()
+    private lateinit var aggregateOldApiTxtsTask: ConcatenateFilesTask
+    private lateinit var aggregateNewApiTxtsTask: ConcatenateFilesTask
+    private lateinit var generateDiffsTask: JDiffTask
 
+    /**
+     * Initialization that should happen only once (and on the root project)
+     */
     @JvmStatic
     fun configureDiffAndDocs(
         root: Project,
@@ -82,6 +91,10 @@
         anchorTask = root.tasks.create("anchorDocsTask")
         val doclavaConfiguration = root.configurations.getByName("doclava")
         val generateSdkApiTask = createGenerateSdkApiTask(root, doclavaConfiguration)
+        val now = LocalDateTime.now()
+        // The diff output assumes that each library is of the same version, but our libraries may each be of different versions
+        // So, we display the date as the new version
+        val newVersion = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))
         rules.forEach {
             val task = createGenerateDocsTask(
                     project = root, generateSdkApiTask = generateSdkApiTask,
@@ -95,7 +108,41 @@
 
         root.tasks.create("generateDocs").dependsOn(docsTasks[TIP_OF_TREE.name])
 
+        val docletClasspath = doclavaConfiguration.resolve()
+
+        aggregateOldApiTxtsTask = root.tasks.create("aggregateOldApiTxts", ConcatenateFilesTask::class.java)
+        aggregateOldApiTxtsTask.Output = File(root.docsDir(), "previous.txt")
+
+        val oldApisTask = root.tasks.createWithConfig("oldApisXml", ApiXmlConversionTask::class.java) {
+            classpath = root.files(docletClasspath)
+            dependsOn(doclavaConfiguration)
+
+            inputApiFile = aggregateOldApiTxtsTask.Output
+            dependsOn(aggregateOldApiTxtsTask)
+
+            outputApiXmlFile = File(root.docsDir(), "previous.xml")
+        }
+
+        aggregateNewApiTxtsTask = root.tasks.create("aggregateNewApiTxts", ConcatenateFilesTask::class.java)
+        aggregateNewApiTxtsTask.Output = File(root.docsDir(), "$newVersion")
+
+        val newApisTask = root.tasks.createWithConfig("newApisXml", ApiXmlConversionTask::class.java) {
+            classpath = root.files(docletClasspath)
+
+            inputApiFile = aggregateNewApiTxtsTask.Output
+            dependsOn(aggregateNewApiTxtsTask)
+
+            outputApiXmlFile = File(root.docsDir(), "$newVersion.xml")
+        }
+
+        val jdiffConfiguration = root.configurations.getByName("jdiff")
+        generateDiffsTask = createGenerateDiffsTask(root,
+                oldApisTask,
+                newApisTask,
+                jdiffConfiguration)
+
         setupDocsProject()
+
         return anchorTask
     }
 
@@ -185,7 +232,7 @@
     }
 
     /**
-     * Registers a Java project for global docs generation, local API file generation, and
+     * Registers a Java project to be included in docs generation, local API file generation, and
      * local API diff generation tasks.
      */
     fun registerJavaProject(project: Project, extension: SupportLibraryExtension) {
@@ -205,16 +252,16 @@
                     "ignoring API tasks.")
             return
         }
-        val tasks = initializeApiChecksForProject(project)
+        val tasks = initializeApiChecksForProject(project, aggregateOldApiTxtsTask, aggregateNewApiTxtsTask)
         registerJavaProjectForDocsTask(tasks.generateApi, compileJava)
-        registerJavaProjectForDocsTask(tasks.generateDiffs, compileJava)
+        registerJavaProjectForDocsTask(generateDiffsTask, compileJava)
         setupDocsTasks(project, tasks)
         anchorTask.dependsOn(tasks.checkApiTask)
     }
 
     /**
-     * Registers an Android project for global docs generation, local API file generation, and
-     * local API diff generation tasks.
+     * Registers an Android project to be included in global docs generation, local API file
+     * generation, and local API diff generation tasks.
      */
     fun registerAndroidProject(
         project: Project,
@@ -248,9 +295,9 @@
                             "an api folder, ignoring API tasks.")
                     return@all
                 }
-                val tasks = initializeApiChecksForProject(project)
+                val tasks = initializeApiChecksForProject(project, aggregateOldApiTxtsTask, aggregateNewApiTxtsTask)
                 registerAndroidProjectForDocsTask(tasks.generateApi, variant)
-                registerAndroidProjectForDocsTask(tasks.generateDiffs, variant)
+                registerAndroidProjectForDocsTask(generateDiffsTask, variant)
                 setupDocsTasks(project, tasks)
                 anchorTask.dependsOn(tasks.checkApiTask)
             }
@@ -259,7 +306,7 @@
 
     private fun setupDocsTasks(project: Project, tasks: Tasks) {
         docsTasks.values.forEach { docs ->
-            tasks.generateDiffs.dependsOn(docs)
+            generateDiffsTask.dependsOn(docs)
             // Track API change history.
             docs.addSinceFilesFrom(project.projectDir)
             // Associate current API surface with the Maven artifact.
@@ -281,21 +328,20 @@
 
 private fun getLastReleasedApiFile(rootFolder: File, refVersion: Version?): File? {
     val apiDir = File(rootFolder, "api")
-    val lastFile = getLastReleasedApiFileFromDir(apiDir, refVersion)
-    if (lastFile != null) {
-        return lastFile
-    }
-
-    return null
+    return getLastReleasedApiFileFromDir(apiDir, refVersion)
 }
 
+/**
+ * Returns the api file with highest version among those having version less than refVersion
+ */
 private fun getLastReleasedApiFileFromDir(apiDir: File, refVersion: Version?): File? {
     var lastFile: File? = null
     var lastVersion: Version? = null
     apiDir.listFiles().forEach { file ->
-        Version.parseOrNull(file)?.let { version ->
-            if ((lastFile == null || lastVersion!! < version) &&
-                    (refVersion == null || version < refVersion)) {
+        val parsed = Version.parseOrNull(file)
+        parsed?.let { version ->
+            if ((lastFile == null || lastVersion!! < version)
+                    && (refVersion == null || version < refVersion)) {
                 lastFile = file
                 lastVersion = version
             }
@@ -327,7 +373,8 @@
     return File(apiDir, "current.txt")
 }
 
-// Generates API files
+
+// Creates a new task on the project for generating API files
 private fun createGenerateApiTask(project: Project, docletpathParam: Collection<File>) =
         project.tasks.createWithConfig("generateApi", DoclavaTask::class.java) {
             setDocletpath(docletpathParam)
@@ -345,6 +392,7 @@
             exclude("**/R.java")
         }
 
+// Creates a new task on the project for verifying the API
 private fun createCheckApiTask(
     project: Project,
     taskName: String,
@@ -437,57 +485,37 @@
         }
 
 /**
- * Converts the <code>fromApi</code>.txt file (or the most recently released
- * X.Y.Z.txt if not explicitly defined using -PfromAPi=<file>) to XML format
- * for use by JDiff.
+ * Returns the filepath of the previous API txt file (for computing diffs against)
  */
-private fun createOldApiXml(project: Project, doclavaConfig: Configuration) =
-        project.tasks.createWithConfig("oldApiXml", ApiXmlConversionTask::class.java) {
-            val toApi = project.processProperty("toApi")?.let {
-                Version.parseOrNull(it)
-            }
-            val fromApi = project.processProperty("fromApi")
-            classpath = project.files(doclavaConfig.resolve())
-            val rootFolder = project.projectDir
-            inputApiFile = if (fromApi != null) {
-                // Use an explicit API file.
-                File(rootFolder, "api/$fromApi.txt")
-            } else {
-                // Use the most recently released API file bounded by toApi.
-                getLastReleasedApiFile(rootFolder, toApi)
-            }
+private fun getOldApiTxt(project: Project): File? {
+    val toApi = project.processProperty("toApi")?.let {
+        Version.parseOrNull(it)
+    }
+    val fromApi = project.processProperty("fromApi")
+    val rootFolder = project.projectDir
+    if (fromApi != null) {
+        // Use an explicit API file.
+        return File(rootFolder, "api/$fromApi.txt")
+    } else {
+        // Use the most recently released API file bounded by toApi.
+        return getLastReleasedApiFile(rootFolder, toApi)
+    }
+}
 
-            outputApiXmlFile = File(project.docsDir(),
-                    "release/${stripExtension(inputApiFile?.name ?: "creation")}.xml")
 
-            dependsOn(doclavaConfig)
-        }
+data class FileProvider(val file: File, val task: Task?)
 
-/**
- * Converts the <code>toApi</code>.txt file (or current.txt if not explicitly
- * defined using -PtoApi=<file>) to XML format for use by JDiff.
- */
-private fun createNewApiXmlTask(
-    project: Project,
-    generateApi: DoclavaTask,
-    doclavaConfig: Configuration
-) =
-        project.tasks.createWithConfig("newApiXml", ApiXmlConversionTask::class.java) {
-            classpath = project.files(doclavaConfig.resolve())
-            val toApi = project.processProperty("toApi")
+private fun getNewApiTxt(project: Project, generateApi: DoclavaTask): FileProvider {
+    val toApi = project.processProperty("toApi")
+    if (toApi != null) {
+        // Use an explicit API file.
+        return FileProvider(File(project.projectDir, "api/$toApi.txt"), null)
+    } else {
+        // Use the current API file (e.g. current.txt).
+        return FileProvider(generateApi.apiFile!!, generateApi)
+    }
 
-            if (toApi != null) {
-                // Use an explicit API file.
-                inputApiFile = File(project.projectDir, "api/$toApi.txt")
-            } else {
-                // Use the current API file (e.g. current.txt).
-                inputApiFile = generateApi.apiFile!!
-                dependsOn(generateApi, doclavaConfig)
-            }
-
-            outputApiXmlFile = File(project.docsDir(),
-                    "release/${stripExtension(inputApiFile?.name ?: "creation")}.xml")
-        }
+}
 
 /**
  * Generates API diffs.
@@ -532,7 +560,7 @@
             newApiXmlFile = newApiTask.outputApiXmlFile
 
             val newApi = newApiXmlFile.name.substringBeforeLast('.')
-            val docsDir = project.rootProject.docsDir()
+            val docsDir = File(project.rootProject.docsDir(), "public")
 
             newJavadocPrefix = "../../../../../reference/"
             destinationDir = File(docsDir, "online/sdk/support_api_diff/${project.name}/$newApi")
@@ -543,6 +571,9 @@
 
             exclude("**/BuildConfig.java", "**/R.java")
             dependsOn(oldApiTask, newApiTask, jdiffConfig)
+            doLast {
+                project.logger.lifecycle("generated diffs into $destinationDir")
+            }
         }
 
 // Generates a distribution artifact for online docs.
@@ -642,11 +673,13 @@
 
 private data class Tasks(
     val generateApi: DoclavaTask,
-    val generateDiffs: JDiffTask,
     val checkApiTask: CheckApiTask
 )
 
-private fun initializeApiChecksForProject(project: Project): Tasks {
+/**
+ * Sets up api tasks for the given project
+ */
+private fun initializeApiChecksForProject(project: Project, aggregateOldApiTxtsTask: ConcatenateFilesTask, aggregateNewApiTxtsTask:ConcatenateFilesTask): Tasks {
     if (!project.hasProperty("docsDir")) {
         project.extensions.add("docsDir", File(project.rootProject.docsDir(), project.name))
     }
@@ -658,7 +691,7 @@
     val generateApi = createGenerateApiTask(project, docletClasspath)
     generateApi.dependsOn(doclavaConfiguration)
 
-    // Make sure the API surface has not broken since the last release.
+    // for verifying that the API surface has not broken since the last release
     val lastReleasedApiFile = getLastReleasedApiFile(workingDir, version)
 
     val whitelistFile = lastReleasedApiFile?.let { apiFile ->
@@ -696,15 +729,20 @@
 
     val updateApiTask = createUpdateApiTask(project, checkApiRelease)
     updateApiTask.dependsOn(checkApiRelease)
-    val newApiTask = createNewApiXmlTask(project, generateApi, doclavaConfiguration)
-    val oldApiTask = createOldApiXml(project, doclavaConfiguration)
 
-    val jdiffConfiguration = project.rootProject.configurations.getByName("jdiff")
-    val generateDiffTask = createGenerateDiffsTask(project,
-            oldApiTask,
-            newApiTask,
-            jdiffConfiguration)
-    return Tasks(generateApi, generateDiffTask, checkApi)
+
+    val oldApiTxt = getOldApiTxt(project)
+    if (oldApiTxt != null) {
+        aggregateOldApiTxtsTask.addInput(project.name, oldApiTxt)
+    }
+    val newApiTxtProvider = getNewApiTxt(project, generateApi)
+    aggregateNewApiTxtsTask.inputs.file(newApiTxtProvider.file)
+    aggregateNewApiTxtsTask.addInput(project.name, newApiTxtProvider.file)
+    if (newApiTxtProvider.task != null) {
+        aggregateNewApiTxtsTask.dependsOn(newApiTxtProvider.task)
+    }
+
+    return Tasks(generateApi, checkApi)
 }
 
 fun hasApiTasks(project: Project, extension: SupportLibraryExtension): Boolean {
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 12160e1..7d81cd8 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -87,7 +87,7 @@
     /**
      * Version code for WorkManager
      */
-    val WORKMANAGER = Version("1.0.0-alpha02")
+    val WORKMANAGER = Version("1.0.0-alpha04")
 
     /**
      * Version code for Jetifier
diff --git a/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
index f66706c..b191d56 100644
--- a/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/checkapi/ApiXmlConversionTask.kt
@@ -23,7 +23,7 @@
 import java.io.File
 
 /**
- * Task that converts the given API file to XML format.
+ * Task that converts the given API txt file to XML format.
  */
 open class ApiXmlConversionTask : JavaExec() {
     @Optional
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/ConcatenateFilesTask.kt b/buildSrc/src/main/kotlin/androidx/build/docs/ConcatenateFilesTask.kt
new file mode 100644
index 0000000..d97c701
--- /dev/null
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/ConcatenateFilesTask.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.build.docs
+
+import org.gradle.api.Action
+import org.gradle.api.DefaultTask
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.OutputFile
+import java.io.File
+import java.util.SortedMap
+
+open class ConcatenateFilesTask : DefaultTask() {
+    private var keyedInputs: MutableMap<String, File> = mutableMapOf()
+
+    @get:OutputFile
+    lateinit var Output: File
+
+    // Adds the given input file
+    // The order that files are concatenated in is based on sorting the corresponding keys
+    fun addInput(key: String, inputFile: File) {
+        if (this.keyedInputs.containsKey(key)) {
+            throw IllegalArgumentException("Key $key already exists")
+        }
+        this.inputs.file(inputFile)
+        this.keyedInputs[key] = inputFile
+    }
+
+    @TaskAction
+    fun aggregate() {
+        val destFile = this.Output
+
+        // sort the input files to make sure this task always concatenates them in the same order
+        val sortedInputs = this.keyedInputs.toSortedMap()
+
+        val inputFiles = sortedInputs.values
+        if (inputFiles.contains(destFile)) {
+            throw IllegalArgumentException("Output file $destFile is also an input file")
+        }
+
+        val text = inputFiles.joinToString(separator = "") { file -> file.readText() }
+        this.project.logger.info("Joining ${inputFiles.count()} files, and storing the result in ${destFile.path}")
+        destFile.writeText(text)
+    }
+}
diff --git a/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt b/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
index 10466ea..54e9ec6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/jdiff/JDiffTask.kt
@@ -33,7 +33,7 @@
 open class JDiffTask : Javadoc() {
 
     /**
-     * Sets the doclet path which has the `com.google.doclava.Doclava` class.
+     * Sets the doclet path, which will be used to locate the `com.google.doclava.Doclava` class.
      *
      *
      * This option will override any doclet path set in this instance's
@@ -82,7 +82,7 @@
     }
 
     /**
-     * "Configures" this JDiffTask with parameters that might not be at their final values
+     * Configures this JDiffTask with parameters that might not be at their final values
      * until this task is run.
      */
     private fun configureJDiffTask() {
diff --git a/car/build.gradle b/car/build.gradle
index 2c65ab4..1eacc0e 100644
--- a/car/build.gradle
+++ b/car/build.gradle
@@ -13,6 +13,7 @@
     api(project(":legacy-support-v4"))
     api(project(":recyclerview"))
     api(project(":gridlayout"))
+    api(project(":preference"))
     api(SUPPORT_DESIGN, libs.exclude_for_material)
 
     androidTestImplementation(TEST_RUNNER_TMP, libs.exclude_for_espresso)
diff --git a/car/res/layout/preference_category_material_car.xml b/car/res/layout/preference_category_material_car.xml
new file mode 100644
index 0000000..2012c69
--- /dev/null
+++ b/car/res/layout/preference_category_material_car.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/car_preference_list_margin"
+    android:background="@drawable/car_card_ripple_background"
+    android:focusable="true"
+    android:orientation="horizontal">
+
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/car_drawer_list_item_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_icon_size" />
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:textAppearance="@style/TextAppearance.Car.Subheader"/>
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorSecondary"/>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/car/res/layout/preference_dropdown_material_car.xml b/car/res/layout/preference_dropdown_material_car.xml
new file mode 100644
index 0000000..932274a
--- /dev/null
+++ b/car/res/layout/preference_dropdown_material_car.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/car_preference_list_margin"
+    android:background="@drawable/car_card_ripple_background"
+    android:focusable="true"
+    android:orientation="horizontal">
+
+    <Spinner
+        android:id="@+id/spinner"
+        android:layout_width="0dp"
+        android:layout_weight="0"
+        android:layout_height="wrap_content"
+        android:visibility="invisible" />
+
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/car_drawer_list_item_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_icon_size" />
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingTop="@dimen/car_preference_list_margin"
+        android:paddingBottom="@dimen/car_preference_list_margin">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.Car.Body1" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.Car.Body2" />
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/car/res/layout/preference_material_car.xml b/car/res/layout/preference_material_car.xml
new file mode 100644
index 0000000..c3049a2
--- /dev/null
+++ b/car/res/layout/preference_material_car.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/car_preference_list_margin"
+    android:background="@drawable/car_card_ripple_background"
+    android:focusable="true">
+
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/car_drawer_list_item_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_icon_size"
+        android:layout_alignParentStart="true"
+        android:layout_centerVertical="true"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:layout_toEndOf="@android:id/icon"
+        android:layout_centerVertical="true">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.Car.Body1" />
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.Car.Body2" />
+
+    </LinearLayout>
+
+    <!-- Preference should place its actual preference widget here. -->
+    <FrameLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_centerVertical="true"
+        android:layout_alignParentEnd="true" />
+
+</RelativeLayout>
diff --git a/car/res/layout/preference_material_car_child.xml b/car/res/layout/preference_material_car_child.xml
new file mode 100644
index 0000000..eec2d0b
--- /dev/null
+++ b/car/res/layout/preference_material_car_child.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingVertical="@dimen/car_preference_list_margin"
+    android:background="@drawable/car_card_ripple_background"
+    android:focusable="true">
+
+    <!-- Wrap in a FrameLayout so the full list item has a touch ripple -->
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="?android:attr/listPreferredItemPaddingStart">
+
+        <androidx.preference.internal.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="@dimen/car_drawer_list_item_small_icon_size"
+            android:layout_height="@dimen/car_drawer_list_item_small_icon_size"
+            android:layout_centerVertical="true"
+            android:layout_alignParentStart="true"/>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:layout_toEndOf="@android:id/icon"
+            android:layout_centerVertical="true">
+
+            <TextView
+                android:id="@android:id/title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:textAppearance="@style/TextAppearance.Car.Label1" />
+
+            <TextView
+                android:id="@android:id/summary"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="@style/TextAppearance.Car.Body3" />
+
+        </LinearLayout>
+
+        <!-- Preference should place its actual preference widget here. -->
+        <FrameLayout
+            android:id="@android:id/widget_frame"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_alignParentEnd="true" />
+
+    </RelativeLayout>
+
+</FrameLayout>
+
diff --git a/car/res/layout/preference_widget_seekbar_material_car.xml b/car/res/layout/preference_widget_seekbar_material_car.xml
new file mode 100644
index 0000000..0064045
--- /dev/null
+++ b/car/res/layout/preference_widget_seekbar_material_car.xml
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<!-- Layout used by SeekBarPreference for the seekbar widget style. -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingVertical="@dimen/car_preference_list_margin"
+    android:orientation="horizontal">
+
+    <androidx.preference.internal.PreferenceImageView
+        android:id="@android:id/icon"
+        android:layout_width="@dimen/car_drawer_list_item_icon_size"
+        android:layout_height="@dimen/car_drawer_list_item_icon_size" />
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:textAppearance="@style/TextAppearance.Car.Body1"/>
+
+        <TextView
+            android:id="@android:id/summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@android:id/title"
+            android:layout_alignStart="@android:id/title"
+            android:textAppearance="@style/TextAppearance.Car.Body2"/>
+
+        <!-- Using UnPressableLinearLayout as a workaround to disable the pressed state propagation
+        to the children of this container layout. Otherwise, the animated pressed state will also
+        play for the thumb in the AbsSeekBar in addition to the preference's ripple background.
+        The background of the SeekBar is also set to null to disable the ripple background -->
+        <androidx.preference.UnPressableLinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_below="@android:id/summary"
+                android:layout_alignStart="@android:id/title"
+                android:clipChildren="false"
+                android:clipToPadding="false">
+            <SeekBar
+                android:id="@+id/seekbar"
+                android:layout_width="0dp"
+                android:layout_weight="1"
+                android:layout_height="wrap_content"
+                android:paddingStart="@dimen/preference_seekbar_padding_start"
+                android:paddingEnd="@dimen/preference_seekbar_padding_end"
+                android:focusable="false"
+                android:clickable="false"
+                android:background="@null" />
+
+            <TextView
+                android:id="@+id/seekbar_value"
+                android:layout_width="@dimen/preference_seekbar_value_width"
+                android:layout_height="match_parent"
+                android:gravity="right|center_vertical"
+                android:textAppearance="@style/TextAppearance.Car.Body2"
+                android:fadingEdge="horizontal"
+                android:scrollbars="none"/>
+        </androidx.preference.UnPressableLinearLayout>
+
+    </RelativeLayout>
+
+</LinearLayout>
diff --git a/car/res/values/dimens.xml b/car/res/values/dimens.xml
index fd8d54d..94c8edc 100644
--- a/car/res/values/dimens.xml
+++ b/car/res/values/dimens.xml
@@ -215,4 +215,8 @@
     <dimen name="speed_bump_lock_out_message_height">96dp</dimen>
     <dimen name="speed_bump_lock_out_drawable_margin_bottom">8dp</dimen>
 
+    <!-- Preferences -->
+    <!-- list margin won't be necessary after PagedListView is brought in -->
+    <dimen name="car_preference_list_margin">8dp</dimen>
+
 </resources>
diff --git a/car/res/values/styles.xml b/car/res/values/styles.xml
index 67ad199..5db209e 100644
--- a/car/res/values/styles.xml
+++ b/car/res/values/styles.xml
@@ -415,4 +415,107 @@
         <item name="android:background">@drawable/car_action_button_background</item>
         <item name="android:tint">@color/car_tint</item>
     </style>
+
+    <!-- ================= -->
+    <!-- Preference Themes -->
+    <!-- ================= -->
+    <eat-comment />
+
+    <style name="CarPreference">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="singleLineTitle">true</item>
+        <item name="iconSpaceReserved">false</item>
+    </style>
+
+    <style name="CarPreference.Information">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="android:enabled">false</item>
+        <item name="android:shouldDisableView">false</item>
+    </style>
+
+    <style name="CarPreference.Category">
+        <item name="android:layout">@layout/preference_category_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+    </style>
+
+    <style name="CarPreference.CheckBoxPreference">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+        <item name="android:widgetLayout">@layout/preference_widget_checkbox</item>
+    </style>
+
+    <style name="CarPreference.SwitchPreferenceCompat">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+        <item name="android:widgetLayout">@layout/preference_widget_switch_compat</item>
+    </style>
+
+    <style name="CarPreference.SwitchPreference">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="singleLineTitle">true</item>
+        <item name="iconSpaceReserved">false</item>
+        <item name="android:widgetLayout">@layout/preference_widget_switch</item>
+    </style>
+
+    <style name="CarPreference.SeekBarPreference">
+        <item name="android:layout">@layout/preference_widget_seekbar_material_car</item>
+        <item name="adjustable">true</item>
+        <item name="showSeekBarValue">true</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+        <item name="android:widgetLayout">@layout/preference_widget_seekbar</item>
+    </style>
+
+    <style name="CarPreference.PreferenceScreen">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+    </style>
+
+    <style name="CarPreference.DialogPreference">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+        <item name="android:positiveButtonText">@android:string/ok</item>
+        <item name="android:negativeButtonText">@android:string/cancel</item>
+    </style>
+
+    <style name="CarPreference.DialogPreference.EditTextPreference">
+        <item name="android:layout">@layout/preference_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="singleLineTitle">true</item>
+        <item name="iconSpaceReserved">false</item>
+        <item name="android:dialogLayout">@layout/preference_dialog_edittext</item>
+    </style>
+
+    <style name="CarPreference.DropDown">
+        <item name="android:layout">@layout/preference_dropdown_material_car</item>
+        <item name="allowDividerAbove">true</item>
+        <item name="allowDividerBelow">true</item>
+        <item name="iconSpaceReserved">false</item>
+    </style>
+
+    <style name="CarPreferenceFragment">
+        <item name="android:divider">@drawable/car_list_divider</item>
+        <item name="allowDividerAfterLastItem">false</item>
+    </style>
+
+    <style name="CarPreferenceFragmentList">
+        <item name="android:paddingLeft">0dp</item>
+        <item name="android:paddingRight">0dp</item>
+    </style>
 </resources>
diff --git a/car/res/values/themes.xml b/car/res/values/themes.xml
index 7f862d7..3030396 100644
--- a/car/res/values/themes.xml
+++ b/car/res/values/themes.xml
@@ -187,4 +187,29 @@
         <item name="listItemTitleTextAppearance">@style/TextAppearance.Car.Body1.Light</item>
         <item name="listItemBodyTextAppearance">@style/TextAppearance.Car.Body2.Light</item>
     </style>
+
+
+    <!-- ================ -->
+    <!-- Preference Theme -->
+    <!-- ================ -->
+    <eat-comment />
+
+    <!-- Car theme for support library PreferenceFragments -->
+    <style name="PreferenceThemeOverlayCar">
+        <item name="preferenceScreenStyle">@style/CarPreference.PreferenceScreen</item>
+        <item name="preferenceFragmentCompatStyle">@style/CarPreferenceFragment</item>
+        <item name="preferenceFragmentStyle">@style/CarPreferenceFragment</item>
+        <item name="preferenceCategoryStyle">@style/CarPreference.Category</item>
+        <item name="preferenceStyle">@style/CarPreference</item>
+        <item name="preferenceInformationStyle">@style/CarPreference.Information</item>
+        <item name="checkBoxPreferenceStyle">@style/CarPreference.CheckBoxPreference</item>
+        <item name="switchPreferenceCompatStyle">@style/CarPreference.SwitchPreferenceCompat</item>
+        <item name="switchPreferenceStyle">@style/CarPreference.SwitchPreference</item>
+        <item name="seekBarPreferenceStyle">@style/CarPreference.SeekBarPreference</item>
+        <item name="dialogPreferenceStyle">@style/CarPreference.DialogPreference</item>
+        <item name="editTextPreferenceStyle">@style/CarPreference.DialogPreference.EditTextPreference</item>
+        <item name="dropdownPreferenceStyle">@style/CarPreference.DropDown</item>
+        <item name="preferenceFragmentListStyle">@style/CarPreferenceFragmentList</item>
+        <item name="android:preferenceLayoutChild">@layout/preference_material_car_child</item>
+    </style>
 </resources>
diff --git a/car/src/androidTest/java/androidx/car/navigation/utils/BundlableTest.java b/car/src/androidTest/java/androidx/car/navigation/utils/BundlableTest.java
new file mode 100644
index 0000000..7f149d0
--- /dev/null
+++ b/car/src/androidTest/java/androidx/car/navigation/utils/BundlableTest.java
@@ -0,0 +1,131 @@
+/*
+ * 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.car.navigation.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link Bundlable}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BundlableTest {
+    /**
+     * Serialization test value.
+     */
+    private static final TestBundlable TEST_VALUE = new TestBundlable()
+            .setInt(123)
+            .setString("TEST")
+            .setEnumValue(TestBundlable.TestEnum.VALUE1)
+            .setListValue(Arrays.asList(
+                    new TestBundlable()
+                            .setString("TEST2")
+                            .setEnumValue(TestBundlable.TestEnum.VALUE2),
+                    new TestBundlable()
+                            .setString("TEST3")
+            ))
+            .setBundlableValue(
+                    new TestBundlable()
+                            .setString("TEST4")
+            );
+
+    /**
+     * Equivalent to {@link #TEST_VALUE} after a schema change (see
+     * {@link TestBundlableNewVersion}). In this new schema, {@link TestBundlable} has its
+     * {@link TestBundlable#mStringValue} field deprecated, and a new
+     * {@link TestBundlableNewVersion#mNewValue} non-null field was added.
+     */
+    private static final TestBundlableNewVersion TEST_VALUE_NEW_VERSION =
+            new TestBundlableNewVersion()
+                    .setInt(123)
+                    .setEnumValue(TestBundlableNewVersion.TestEnum.VALUE1)
+                    .setListValue(Arrays.asList(
+                            new TestBundlableNewVersion()
+                                    .setEnumValue(TestBundlableNewVersion.TestEnum.VALUE2),
+                            new TestBundlableNewVersion()
+                    ))
+                    .setBundlableValue(
+                            new TestBundlableNewVersion()
+                    );
+
+    /**
+     * Expected value when interpreting {@link #TEST_VALUE_NEW_VERSION} using the same schema as
+     * {@link TestBundlable}. Given that {@link TestBundlableNewVersion#mNewValue} doesn't exist
+     * the old schema, and {@link TestBundlable#mStringValue} doesn't exist in the new schema,
+     * both values are dropped during serialization/deserialization.
+     */
+    private static final TestBundlable TEST_VALUE_NEW_VERSION_OLD_SCHEMA = new TestBundlable()
+            .setInt(123)
+            .setEnumValue(TestBundlable.TestEnum.VALUE1)
+            .setListValue(Arrays.asList(
+                    new TestBundlable()
+                            .setEnumValue(TestBundlable.TestEnum.VALUE2),
+                    new TestBundlable()
+            ))
+            .setBundlableValue(
+                    new TestBundlable()
+            );
+
+    private BundleMarshaller mBundleMarshaller = new BundleMarshaller();
+
+    /**
+     * Asserts that serializing and deserializing a {@link Bundlable} produces the same content.
+     * This includes testing instances with null values in them.
+     */
+    @Test
+    public void testSerializationDeserializationMaintainsContent() {
+        TestBundlable output = new TestBundlable();
+
+        TEST_VALUE.toBundle(mBundleMarshaller);
+        output.fromBundle(mBundleMarshaller);
+        assertEquals(TEST_VALUE, output);
+    }
+
+    /**
+     * Asserts that serialization and deserialization works in a forward compatible way, as long as
+     * they follow the versioning rules listed in {@link Bundlable}.
+     */
+    @Test
+    public void testForwardCompatibleChangesMaintainsCommonContent() {
+        TestBundlableNewVersion output = new TestBundlableNewVersion();
+
+        TEST_VALUE.toBundle(mBundleMarshaller);
+        output.fromBundle(mBundleMarshaller);
+        assertEquals(TEST_VALUE_NEW_VERSION, output);
+    }
+
+    /**
+     * Asserts that serialization and deserialization works in a backwards compatible way, as long
+     * as they follow the versioning rules listed in {@link Bundlable}.
+     */
+    @Test
+    public void testBackwardCompatibleChangesMaintainsCommonContent() {
+        TestBundlable output = new TestBundlable();
+
+        TEST_VALUE_NEW_VERSION.toBundle(mBundleMarshaller);
+        output.fromBundle(mBundleMarshaller);
+        assertEquals(TEST_VALUE_NEW_VERSION_OLD_SCHEMA, output);
+    }
+}
diff --git a/car/src/androidTest/java/androidx/car/navigation/utils/BundleMarshallerTest.java b/car/src/androidTest/java/androidx/car/navigation/utils/BundleMarshallerTest.java
new file mode 100644
index 0000000..cf5ac15
--- /dev/null
+++ b/car/src/androidTest/java/androidx/car/navigation/utils/BundleMarshallerTest.java
@@ -0,0 +1,219 @@
+/*
+ * 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.car.navigation.utils;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Unit tests for {@link BundleMarshaller}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BundleMarshallerTest {
+    private final BundleMarshaller mBundleMarshaller = new BundleMarshaller();
+
+    /**
+     * A random test value with a mix of primitives, null and non-null data.
+     */
+    private static final TestBundlable TEST_VALUE = new TestBundlable()
+            .setInt(1)
+            .setString("TEST")
+            .setBundlableValue(new TestBundlable()
+                    .setEnumValue(TestBundlable.TestEnum.VALUE1));
+
+    /**
+     * Tests that null values are serialized as expected.
+     */
+    @Test
+    public void serialization_nullCase() {
+        new TestBundlable().toBundle(mBundleMarshaller);
+
+        Bundle data = mBundleMarshaller.getBundle();
+        assertTrue(data.containsKey("intValue"));
+        assertEquals(0, data.getInt("intValue"));
+        assertNull(data.getString("stringValue"));
+        assertNull(data.getString("enumValue"));
+        assertTrue(data.getBoolean("bundlableValue._isNull"));
+        assertEquals(-1, data.getInt("bundlableListValue._size"));
+    }
+
+    /**
+     * Tests that nested {@link Bundlable}s are serialized as expected.
+     */
+    @Test
+    public void serialization_nestedBundlable() {
+        String stringValue = "TEST";
+        int intValue = 1;
+
+        new TestBundlable()
+                .setBundlableValue(new TestBundlable()
+                        .setInt(intValue)
+                        .setString(stringValue))
+                .toBundle(mBundleMarshaller);
+
+        Bundle data = mBundleMarshaller.getBundle();
+        assertTrue(data.containsKey("bundlableValue._isNull"));
+        assertFalse(data.getBoolean("bundlableValue._isNull"));
+        assertEquals(intValue, data.getInt("bundlableValue.intValue"));
+        assertEquals(stringValue, data.getString("bundlableValue.stringValue"));
+    }
+
+    /**
+     * Tests the correct serialization of a list of size 0.
+     */
+    @Test
+    public void listSerialization_listOfSize0() {
+        TestBundlable value = new TestBundlable().setListValue(new ArrayList<>());
+        value.toBundle(mBundleMarshaller);
+        Bundle data = mBundleMarshaller.getBundle();
+        assertEquals(0, data.getInt("bundlableListValue._size"));
+    }
+
+    /**
+     * Tests the correct serialization of a list of size n.
+     */
+    @Test
+    public void listSerialization_listOfSizeN() {
+        TestBundlable value = new TestBundlable().setListValue(Arrays.asList(
+                new TestBundlable().setInt(1),
+                new TestBundlable().setInt(2),
+                new TestBundlable().setInt(3)));
+        Bundle data = mBundleMarshaller.getBundle();
+        value.toBundle(mBundleMarshaller);
+        assertEquals(3, data.getInt("bundlableListValue._size"));
+        assertEquals(1, data.getInt("bundlableListValue.0.intValue"));
+        assertEquals(2, data.getInt("bundlableListValue.1.intValue"));
+        assertEquals(3, data.getInt("bundlableListValue.2.intValue"));
+    }
+
+    @Test
+    public void listSerialization_removingElementsInPlace() {
+        // Serialize and deserialize a list of a certain size
+        List<TestBundlable> mutableList = new ArrayList<>(Arrays.asList(
+                new TestBundlable().setInt(1),
+                new TestBundlable().setInt(2),
+                new TestBundlable().setInt(3)));
+        TestBundlable out = new TestBundlable().setListValue(mutableList);
+        TestBundlable in = new TestBundlable();
+        out.toBundle(mBundleMarshaller);
+        in.fromBundle(mBundleMarshaller);
+        assertEquals(out, in);
+
+        // Remove some elements and check that they are correctly removed during deserialization
+        mutableList.remove(0);
+        out.toBundle(mBundleMarshaller);
+        in.fromBundle(mBundleMarshaller);
+        assertEquals(out, in);
+    }
+
+    /**
+     * Tests that {@link BundleMarshaller#getDelta()} returns the same value as
+     * {@link BundleMarshaller#getBundle()} during the initial serialization.
+     */
+    @Test
+    public void deltaSerialization_equalsFullDataIfNotReset() {
+        TEST_VALUE.toBundle(mBundleMarshaller);
+        Bundle data = mBundleMarshaller.getBundle();
+        Bundle delta = mBundleMarshaller.getDelta();
+        assertBundlesEqual(data, delta);
+    }
+
+    /**
+     * Tests that {@link BundleMarshaller#getDelta()} is empty if no data is modified between
+     * serializations.
+     */
+    @Test
+    public void deltaSerialization_emptyIfNoDataIsModified() {
+        TEST_VALUE.toBundle(mBundleMarshaller);
+        mBundleMarshaller.resetDelta();
+        TEST_VALUE.toBundle(mBundleMarshaller);
+        Bundle delta = mBundleMarshaller.getDelta();
+        assertEquals(0, delta.size());
+    }
+
+    /**
+     * Tests that {@link BundleMarshaller#getDelta()} returns only the data that has been modified
+     * between two serializations.
+     */
+    @Test
+    public void deltaSerialization_onlyContainsModifiedData() {
+        // Serialize some base data
+        TestBundlable testValue = new TestBundlable()
+                .setInt(1)
+                .setString("TEST")
+                .setBundlableValue(new TestBundlable()
+                        .setEnumValue(TestBundlable.TestEnum.VALUE1));
+        testValue.toBundle(mBundleMarshaller);
+
+        // Reset change tracking and re-serialize after making some changes.
+        mBundleMarshaller.resetDelta();
+        testValue.setInt(2).setString(null);
+        testValue.mBundableValue.setEnumValue(TestBundlable.TestEnum.VALUE2);
+        testValue.toBundle(mBundleMarshaller);
+
+        Bundle expectedDelta = new Bundle();
+        expectedDelta.putInt("intValue", testValue.mIntValue);
+        expectedDelta.putString("stringValue", testValue.mStringValue);
+        expectedDelta.putString("bundlableValue.enumValue",
+                testValue.mBundableValue.mEnumValue.name());
+
+        Bundle delta = mBundleMarshaller.getDelta();
+        assertBundlesEqual(expectedDelta, delta);
+    }
+
+    /**
+     * Asserts that the provided {@link Bundle}s are equal. It throws {@link AssertionError}
+     * otherwise.
+     */
+    private void assertBundlesEqual(Bundle expected, Bundle actual) {
+        if (expected == null && actual == null) {
+            return;
+        }
+        if (expected == null || actual == null) {
+            fail(String.format("Expected %s value but found %s",
+                    expected != null ? "non-null" : "null",
+                    actual != null ? "non-null" : "null"));
+        }
+        if (!expected.keySet().equals(actual.keySet())) {
+            fail(String.format("Expected keys: %s, but found keys: %s",
+                    expected.keySet().stream().sorted().collect(Collectors.joining(",")),
+                    actual.keySet().stream().sorted().collect(Collectors.joining(","))));
+        }
+        for (String key : expected.keySet()) {
+            assertEquals(String.format("Expected '%s' at key '%s' but found '%s",
+                            expected.get(key), key, actual.get(key)),
+                    expected.get(key),
+                    actual.get(key));
+        }
+    }
+}
diff --git a/car/src/androidTest/java/androidx/car/navigation/utils/TestBundlable.java b/car/src/androidTest/java/androidx/car/navigation/utils/TestBundlable.java
new file mode 100644
index 0000000..1ca19d3
--- /dev/null
+++ b/car/src/androidTest/java/androidx/car/navigation/utils/TestBundlable.java
@@ -0,0 +1,107 @@
+/*
+ * 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.car.navigation.utils;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Reference implementation of a {@link Bundlable}
+ */
+class TestBundlable implements Bundlable {
+    private static final String INT_VALUE_KEY = "intValue";
+    private static final String STRING_VALUE_KEY = "stringValue";
+    private static final String ENUM_VALUE_KEY = "enumValue";
+    private static final String BUNDLABLE_VALUE_KEY = "bundlableValue";
+    private static final String BUNDLABLE_LIST_VALUE_KEY = "bundlableListValue";
+
+    enum TestEnum {
+        VALUE1,
+        VALUE2,
+        VALUE3,
+    }
+
+    int mIntValue;
+    String mStringValue;
+    TestEnum mEnumValue;
+    TestBundlable mBundableValue;
+    List<TestBundlable> mListValue;
+
+    @Override
+    public void toBundle(BundleMarshaller out) {
+        out.putInt(INT_VALUE_KEY, mIntValue);
+        out.putString(STRING_VALUE_KEY, mStringValue);
+        out.putEnum(ENUM_VALUE_KEY, mEnumValue);
+        out.putBundlable(BUNDLABLE_VALUE_KEY, mBundableValue);
+        out.putBundlableList(BUNDLABLE_LIST_VALUE_KEY, mListValue);
+    }
+
+    @Override
+    public void fromBundle(BundleMarshaller in) {
+        mIntValue = in.getInt(INT_VALUE_KEY);
+        mStringValue = in.getString(STRING_VALUE_KEY);
+        mEnumValue = in.getEnum(ENUM_VALUE_KEY, TestEnum.class);
+        mBundableValue = in.getBundlable(BUNDLABLE_VALUE_KEY, mBundableValue, TestBundlable::new);
+        mListValue = in.getBundlableList(BUNDLABLE_LIST_VALUE_KEY, mListValue, TestBundlable::new);
+    }
+
+    public TestBundlable setInt(int value) {
+        mIntValue = value;
+        return this;
+    }
+
+    public TestBundlable setString(String value) {
+        mStringValue = value;
+        return this;
+    }
+
+    public TestBundlable setEnumValue(TestEnum value) {
+        mEnumValue = value;
+        return this;
+    }
+
+    public TestBundlable setBundlableValue(TestBundlable value) {
+        mBundableValue = value;
+        return this;
+    }
+
+    public TestBundlable setListValue(List<TestBundlable> value) {
+        mListValue = value;
+        return this;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TestBundlable that = (TestBundlable) o;
+        return mIntValue == that.mIntValue
+                && Objects.equals(mStringValue, that.mStringValue)
+                && mEnumValue == that.mEnumValue
+                && Objects.equals(mBundableValue, that.mBundableValue)
+                && Objects.equals(mListValue, that.mListValue);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIntValue, mStringValue, mEnumValue, mBundableValue, mListValue);
+    }
+}
diff --git a/car/src/androidTest/java/androidx/car/navigation/utils/TestBundlableNewVersion.java b/car/src/androidTest/java/androidx/car/navigation/utils/TestBundlableNewVersion.java
new file mode 100644
index 0000000..58ee6f6
--- /dev/null
+++ b/car/src/androidTest/java/androidx/car/navigation/utils/TestBundlableNewVersion.java
@@ -0,0 +1,132 @@
+/*
+ * 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.car.navigation.utils;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Preconditions;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Example of a version change. This is a copy of {@link TestBundlable} but with one field
+ * deprecated ({@link TestBundlable#mStringValue}) and one field added
+ * ({@link TestBundlableNewVersion#mNewValue})
+ */
+class TestBundlableNewVersion implements Bundlable {
+    private static final String INT_VALUE_KEY = "intValue";
+    // In this version of TestBundlable, mStringValue field has been deprecated, and its key should
+    // not be re-used.
+    // private static final String STRING_VALUE_KEY = "stringValue";
+    private static final String ENUM_VALUE_KEY = "enumValue";
+    private static final String BUNDLABLE_VALUE_KEY = "bundlableValue";
+    private static final String BUNDLABLE_LIST_VALUE_KEY = "bundlableListValue";
+    private static final String NEW_VALUE_KEY = "newValue";
+
+    enum TestEnum {
+        VALUE1,
+        VALUE2,
+        VALUE3,
+    }
+
+    public static final String DEFAULT_NEW_VALUE = "TEST_V2";
+
+    int mIntValue;
+    TestEnum mEnumValue;
+    // In this version of TestBundlable, we simulate mStringValue being deprecated.
+    // String mStringValue;
+    TestBundlableNewVersion mBundableValue;
+    List<TestBundlableNewVersion> mListValue;
+    // In this version of TestBundlable, we simulate the creation of a new mandatory field.
+    @NonNull
+    String mNewValue;
+
+    TestBundlableNewVersion() {
+        mNewValue = DEFAULT_NEW_VALUE;
+    }
+
+    @Override
+    public void toBundle(@NonNull BundleMarshaller out) {
+        out.putInt(INT_VALUE_KEY, mIntValue);
+        // dest.putString(STRING_VALUE_KEY, mStringValue) is now deprecated.
+        out.putEnum(ENUM_VALUE_KEY, mEnumValue);
+        out.putBundlable(BUNDLABLE_VALUE_KEY, mBundableValue);
+        out.putBundlableList(BUNDLABLE_LIST_VALUE_KEY, mListValue);
+        // new required value
+        out.putString(NEW_VALUE_KEY, mNewValue);
+    }
+
+    @Override
+    public void fromBundle(@NonNull BundleMarshaller in) {
+        mIntValue = in.getInt(INT_VALUE_KEY);
+        // mStringValue = in.getString(STRING_VALUE_KEY) is now deprecated.
+        mEnumValue = in.getEnum(ENUM_VALUE_KEY, TestEnum.class);
+        mBundableValue = in.getBundlable(BUNDLABLE_VALUE_KEY, mBundableValue,
+                TestBundlableNewVersion::new);
+        mListValue = in.getBundlableList(BUNDLABLE_LIST_VALUE_KEY, mListValue,
+                TestBundlableNewVersion::new);
+        // new required value with a mandatory default
+        mNewValue = in.getStringNonNull(NEW_VALUE_KEY, DEFAULT_NEW_VALUE);
+    }
+
+    public TestBundlableNewVersion setInt(int value) {
+        mIntValue = value;
+        return this;
+    }
+
+    public TestBundlableNewVersion setEnumValue(TestEnum value) {
+        mEnumValue = value;
+        return this;
+    }
+
+    public TestBundlableNewVersion setBundlableValue(TestBundlableNewVersion value) {
+        mBundableValue = value;
+        return this;
+    }
+
+    public TestBundlableNewVersion setListValue(List<TestBundlableNewVersion> value) {
+        mListValue = value;
+        return this;
+    }
+
+    public TestBundlableNewVersion setNewValue(@NonNull String value) {
+        Preconditions.checkNotNull(value);
+        mNewValue = value;
+        return this;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        TestBundlableNewVersion that = (TestBundlableNewVersion) o;
+        return mIntValue == that.mIntValue
+                && mEnumValue == that.mEnumValue
+                && Objects.equals(mBundableValue, that.mBundableValue)
+                && Objects.equals(mListValue, that.mListValue)
+                && Objects.equals(mNewValue, that.mNewValue);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIntValue, mEnumValue, mBundableValue, mListValue, mNewValue);
+    }
+}
diff --git a/car/src/main/java/androidx/car/navigation/utils/Bundlable.java b/car/src/main/java/androidx/car/navigation/utils/Bundlable.java
new file mode 100644
index 0000000..65e8bf2
--- /dev/null
+++ b/car/src/main/java/androidx/car/navigation/utils/Bundlable.java
@@ -0,0 +1,99 @@
+/*
+ * 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.car.navigation.utils;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+
+/**
+ * Interface for classes whose instances can be written to and restored from a {@link Bundle}.
+ * Classes implementing the {@link Bundlable} interface must also provide a public default
+ * constructor, or a constructor that accepts {@link BundleMarshaller} as its only parameter.
+ * <p>
+ * This serialization protocol is designed to:
+ * <ul>
+ * <li>provide backward/forward compatibility between producers and consumers (following the
+ * Protocol Buffers pattern).
+ * <li>minimize the number of objects being allocated during both serialization and deserialization.
+ * </ul>
+ * <p>
+ * Implementations of this interface should comply to the following rules:
+ * <ul>
+ * <li>Fields should be serialized and deserialized using {@link BundleMarshaller} "put" and
+ * "get" methods.
+ * <li>Marshalling keys must be lower camel case alphanumerical identifiers (i.e.: "distanceUnit").
+ * (symbols such as "." and "_" are reserved by the system).
+ * <li>Field types should not be modified between versions of {@link Bundlable} objects to provide
+ * backward and forward compatibility. Only deprecations and additions are allowed.
+ * <li>When a field is deprecated, its marshalling key shouldn't be reused by any new field.
+ * <li>Enums are marshalled using {@link Enum#name()}. Because of this, enum values should not be
+ * renamed. Because enum values could be added or deprecated, clients must be prepared to accept
+ * null or a default value in case the server sends a value it doesn't know.
+ * <li>Fields annotated with {@link androidx.annotation.NonNull} should not be deprecated
+ * (as clients might not be prepared for their absence). Implementations of this interface should
+ * enforce this constraint, i.e. by initializing these fields at class instantiation, and using
+ * {@link androidx.core.util.Preconditions#checkNotNull(Object)} to prevent null values. If a new
+ * {@link androidx.annotation.NonNull} field is added on an existing {@link Bundlable}, the
+ * deserialization must provide a default value for it (as existing services won't provide values
+ * for it until they are updated).
+ * </ul>
+ * The following is an example of the suggested implementation:
+ * <pre>
+ * public class MyClass implements Bundlable {
+ *     private static final String FOO_VALUE_KEY = "fooValue";
+ *     private static final String BAR_VALUE_KEY = "barValue";
+ *
+ *     public enum MyEnum {
+ *         VALUE_1,
+ *         VALUE_2
+ *     }
+ *
+ *     public String mFooValue;
+ *     public MyEnum mBarValue;
+ *
+ *     &#064;Override
+ *     public void toBundle(@NonNull BundleMarshaller out) {
+ *         out.putString(FOO_VALUE_KEY, mFooValue);
+ *         out.putEnum(BAR_VALUE_KEY, mBarValue);
+ *     }
+ *
+ *     &#064;Override
+ *     public void fromBundle(@NonNull BundleMarshaller in) {
+ *         mFooValue = in.getString(FOO_VALUE_KEY);
+ *         mBarValue = in.getEnum(BAR_VALUE_KEY, MyEnum.class);
+ *     }
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public interface Bundlable {
+    /**
+     * Serializes this object into a {@link BundleMarshaller} by writing all its fields to it.
+     */
+    void toBundle(@NonNull BundleMarshaller out);
+
+    /**
+     * Deserializes this object from a {@link BundleMarshaller} by reading all its fields from it.
+     */
+    void fromBundle(@NonNull BundleMarshaller in);
+}
diff --git a/car/src/main/java/androidx/car/navigation/utils/BundleMarshaller.java b/car/src/main/java/androidx/car/navigation/utils/BundleMarshaller.java
new file mode 100644
index 0000000..1e6178a
--- /dev/null
+++ b/car/src/main/java/androidx/car/navigation/utils/BundleMarshaller.java
@@ -0,0 +1,514 @@
+/*
+ * 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.car.navigation.utils;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+/**
+ * Class responsible for serializing and deserializing data into a {@link Bundle}. It also
+ * provides a way to detect what items in the {@link Bundle} have been modified during
+ * marshalling.
+ * <p>
+ * A single {@link BundleMarshaller} can be re-used to serialize or deserialize data multiple times.
+ * Similarity, deserialization can be done in-place, updating existing {@link Bundlable}s. This
+ * reduces the number of instances being allocated.
+ * <p>
+ * When serializing, use {@link #resetBundle()} before marshalling and {@link #getBundle()} to
+ * obtain an snap-shot of the serialized content. Or use {@link #resetDelta()} and
+ * {@link #getDelta()} to obtain a {@link Bundle} representing the patch between the last and
+ * the new serialized data.
+ * <p>
+ * When deserializing, use {@link #setBundle(Bundle)} to deserialize a {@link Bundle} containing an
+ * snap-shot, or {@link #applyDelta(Bundle)} to process a patch from the last deserialized data.
+ * <p>
+ * Keys used in the "get" and "put" methods must be lower camel case alphanumerical identifiers
+ * (e.g.: "distanceUnit"). Symbols like "." and "_" are reserved by the system.
+ * <p>
+ * When deserializing {@link List} objects, this class assumes that they implement random access
+ * (e.g. {@link ArrayList}), or they are relatively small (see more details at
+ * {@link #trimList(List, int)})
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public class BundleMarshaller {
+    /**
+     * Separator used to concatenate identifiers when marshalling non-primitive types (e.g. lists
+     * or {@link Bundlable}s).
+     */
+    private static final String KEY_SEPARATOR = ".";
+    /**
+     * Identifier used to record if a given non-primitive field is null or not. This allows
+     * serializing null objects without the need of using reflection or static methods.
+     */
+    private static final String IS_NULL_KEY = "_isNull";
+    /**
+     * Identifier used to record the length of a collection. This allows serializing changes to the
+     * length of a collection without having to remove elements or having to iterate over every
+     * possible collection key.
+     */
+    private static final String SIZE_KEY = "_size";
+    /**
+     * Special value for {@link #SIZE_KEY} to serialize a null collection.
+     */
+    private static final int NULL_SIZE = -1;
+
+    private Bundle mBundle = new Bundle();
+    private String mKeyPrefix = "";
+    private final Bundle mBundleDelta = new Bundle();
+
+    /**
+     * Returns data serialized since the last time this instance was constructed, or
+     * {@link #resetBundle()} was called.
+     */
+    public Bundle getBundle() {
+        return mBundle;
+    }
+
+    /**
+     * Resets this {@link BundleMarshaller} causing {@link #getBundle()} to return an empty
+     * {@link Bundle} until the next marshalling is executed. This can be used occasionally to
+     * remove unused keys in the {@link Bundle}.
+     */
+    public void resetBundle() {
+        mBundle.clear();
+    }
+
+    /**
+     * Replaces the {@link Bundle} to serialize into or deserialize from.
+     */
+    public void setBundle(Bundle bundle) {
+        mBundle = bundle;
+    }
+
+    /**
+     * Gets a {@link Bundle} containing only the entries of {@link #getBundle()} that were modified
+     * since this instance was constructed, or {@link #resetDelta()} was called.
+     */
+    public Bundle getDelta() {
+        return mBundleDelta;
+    }
+
+    /**
+     * Merges the provided {@link Bundle} on top of the one stored in this {@link BundleMarshaller}.
+     *
+     * @param delta a {@link Bundle} containing entries to be updated on one stored in this
+     *              {@link BundleMarshaller} instance. Such {@link Bundle} can be produced by
+     *              using the {@link #resetDelta()} and {@link #getDelta()} methods during data
+     *              serialization.
+     */
+    public void applyDelta(Bundle delta) {
+        mBundle.putAll(delta);
+    }
+
+    /**
+     * Resets tracking of modified entries, causing {@link #getDelta()} to return an empty
+     * {@link Bundle} until the next marshalling is executed. This can be used between
+     * serializations make {@link #getDelta()} return only the differences.
+     */
+    public void resetDelta() {
+        mBundleDelta.clear();
+    }
+
+    /**
+     * Inserts an int value, replacing any existing value for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value an int
+     */
+    public void putInt(@NonNull String key, int value) {
+        String mangledKey = getMangledKey(key);
+        if (!mBundle.containsKey(mangledKey) || mBundle.getInt(mangledKey) != value) {
+            mBundleDelta.putInt(mangledKey, value);
+            mBundle.putInt(mangledKey, value);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0 if no mapping of the desired type
+     * exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @return an int
+     */
+    public int getInt(@NonNull String key) {
+        return mBundle.getInt(getMangledKey(key));
+    }
+
+    /**
+     * Inserts a float value, replacing any existing value for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value a float
+     */
+    public void putFloat(@NonNull String key, float value) {
+        String mangledKey = getMangledKey(key);
+        if (!mBundle.containsKey(mangledKey)
+                || Float.compare(mBundle.getFloat(mangledKey), value) != 0) {
+            mBundleDelta.putFloat(mangledKey, value);
+            mBundle.putFloat(mangledKey, value);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0f if no mapping of the desired type
+     * exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @return a float
+     */
+    public float getFloat(@NonNull String key) {
+        return mBundle.getFloat(getMangledKey(key));
+    }
+
+    /**
+     * Inserts a double value, replacing any existing value for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value a double
+     */
+    public void putDouble(@NonNull String key, double value) {
+        String mangledKey = getMangledKey(key);
+        if (!mBundle.containsKey(mangledKey)
+                || Double.compare(mBundle.getDouble(mangledKey), value) != 0) {
+            mBundleDelta.putDouble(mangledKey, value);
+            mBundle.putDouble(mangledKey, value);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or 0.0 if no mapping of the desired type
+     * exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @return a double
+     */
+    public double getDouble(@NonNull String key) {
+        return mBundle.getDouble(getMangledKey(key));
+    }
+
+    /**
+     * Inserts a boolean value, replacing any existing value for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value a boolean
+     */
+    public void putBoolean(@NonNull String key, boolean value) {
+        String mangledKey = getMangledKey(key);
+        if (!mBundle.containsKey(mangledKey) || mBundle.getBoolean(mangledKey) != value) {
+            mBundleDelta.putBoolean(mangledKey, value);
+            mBundle.putBoolean(mangledKey, value);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or false if no mapping of the desired type
+     * exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @return a boolean
+     */
+    public boolean getBoolean(@NonNull String key) {
+        return mBundle.getBoolean(getMangledKey(key));
+    }
+
+    /**
+     * Inserts a string value, replacing any existing value for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value a string, or null
+     */
+    public void putString(@NonNull String key, @Nullable String value) {
+        String mangledKey = getMangledKey(key);
+        if (!mBundle.containsKey(mangledKey)
+                || !Objects.equals(mBundle.getString(mangledKey), value)) {
+            mBundleDelta.putString(mangledKey, value);
+            mBundle.putString(mangledKey, value);
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of the desired type
+     * exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @return a string, or null
+     */
+    @Nullable
+    public String getString(@NonNull String key) {
+        return mBundle.getString(getMangledKey(key));
+    }
+
+    /**
+     * Returns the value associated with the given key, or the provided default value if no mapping
+     * of the desired type exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param defaultValue value to return if key does not exist or if a null value is associated
+     *                     with the given key.
+     * @return a string
+     */
+    @NonNull
+    public String getStringNonNull(@NonNull String key, @NonNull String defaultValue) {
+        return mBundle.getString(getMangledKey(key), defaultValue);
+    }
+
+    /**
+     * Inserts an enum value, replacing any existing value for the given key. The provided enum
+     * will be serialized as a string using {@link Enum#name()}.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value an enum, or null
+     */
+    public <T extends Enum<T>> void putEnum(@NonNull String key, @Nullable T value) {
+        putString(key, value != null ? value.name() : null);
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of the desired type
+     * exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param clazz {@link Enum} class to be used to deserialize the value.
+     * @param <T> {@link Enum} type to be returned.
+     * @return an enum, or null
+     */
+    @Nullable
+    public <T extends Enum<T>> T getEnum(@NonNull String key, @NonNull Class<T> clazz) {
+        String name = getString(key);
+        try {
+            return name != null ? Enum.valueOf(clazz, name) : null;
+        } catch (IllegalArgumentException ex) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the value associated with the given key, or the provided default value if no mapping
+     * of the desired type exists for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param clazz {@link Enum} class to be used to deserialize the value.
+     * @param defaultValue value to return if key does not exist or if a null value is associated
+     *                     with the given key.
+     * @param <T> {@link Enum} type to be returned.
+     * @return an enum
+     */
+    @NonNull
+    public <T extends Enum<T>> T getEnumNonNull(@NonNull String key, @NonNull Class<T> clazz,
+            @NonNull T defaultValue) {
+        T result = getEnum(key, clazz);
+        return result != null ? result : defaultValue;
+    }
+
+    /**
+     * Inserts a {@link Bundlable} value, replacing any existing value for the given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param value a {@link Bundlable}, or null
+     */
+    public <T extends Bundlable> void putBundlable(@NonNull String key, @Nullable T value) {
+        withKeyPrefix(key, () -> {
+            putBoolean(IS_NULL_KEY, value == null);
+            if (value != null) {
+                value.toBundle(this);
+            }
+        });
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of the desired type
+     * exists for the given key. If a non-null "current" instance is provided, then the
+     * deserialization would be done in place. Otherwise, a new instance will be created using the
+     * provided factory.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param current current value (if available) to perform in-place deserialization, or null
+     * @param factory a {@link Supplier} capable of providing an instance of a {@link Bundlable} of
+     *                type T. The suggested implementation is to pass a reference to the default
+     *                constructor of that class.
+     * @param <T> {@link Bundlable} type to be returned.
+     * @return an instance of type T, or null
+     */
+    @Nullable
+    public <T extends Bundlable> T getBundlable(@NonNull String key, @Nullable T current,
+            @NonNull Supplier<T> factory) {
+        return withKeyPrefix(key, () -> {
+            if (getBoolean(IS_NULL_KEY)) {
+                return null;
+            }
+            T result = current != null ? current : factory.get();
+            result.fromBundle(this);
+            return result;
+        });
+    }
+
+    /**
+     * Returns the value associated with the given key, or a default value if no mapping of the
+     * desired type exists for the given key. If a non-null value is available, then such value
+     * will be deserialized in-place on the given "current" instance. Otherwise, a default value
+     * will be generated using the provided factory.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param current current value to perform in-place deserialization
+     * @param factory a {@link Supplier} capable of providing an instance of a {@link Bundlable} of
+     *                type T. The suggested implementation is to pass a reference to the default
+     *                constructor of that class.
+     * @param <T> {@link Bundlable} type to be returned.
+     * @return an instance of type T
+     */
+    @NonNull
+    public <T extends Bundlable> T getBundlableNonNull(@NonNull String key, @NonNull T current,
+            @NonNull Supplier<T> factory) {
+        T result = getBundlable(key, current, factory);
+        return result != null ? result : factory.get();
+    }
+
+    /**
+     * Inserts a {@link List} of {@link Bundlable} values, replacing any existing value for the
+     * given key.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param values a {@link List} of {@link Bundlable} values, or null
+     */
+    public <T extends Bundlable> void putBundlableList(@NonNull String key,
+            @Nullable List<T> values) {
+        withKeyPrefix(key, () -> {
+            putInt(SIZE_KEY, values != null ? values.size() : NULL_SIZE);
+            if (values != null) {
+                int pos = 0;
+                // Using for-each as the provided list might not implement random access (e.g. it
+                // might be a linked list).
+                for (T value : values) {
+                    putBundlable(String.valueOf(pos), value);
+                    pos++;
+                }
+            }
+        });
+    }
+
+    /**
+     * Returns the value associated with the given key, or null if no mapping of the desired type
+     * exists for the given key. If a non-null "current" list is provided, then the deserialization
+     * would be done in place. Otherwise, a new list will be created and items will be instantiated
+     * using the provided factory.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param current current value (if available) to perform in-place deserialization, or null
+     * @param factory a {@link Supplier} capable of providing an instance of a {@link Bundlable} of
+     *                type T. The suggested implementation is to pass a reference to the default
+     *                constructor of that class.
+     * @param <T> {@link Bundlable} type to be returned.
+     * @return a list of instances of type T, or null. The resulting list might contain null
+     *         elements.
+     */
+    @Nullable
+    public <T extends Bundlable> List<T> getBundlableList(@NonNull String key,
+            @Nullable List<T> current, @NonNull Supplier<T> factory) {
+        return withKeyPrefix(key, () -> {
+            int listSize = getInt(SIZE_KEY);
+            if (listSize == NULL_SIZE) {
+                return null;
+            }
+            List<T> result = current != null ? current : new ArrayList<>(listSize);
+            if (result.size() > listSize) {
+                result.subList(listSize, result.size()).clear();
+            }
+            for (int pos = 0; pos < listSize; pos++) {
+                String subKey = String.valueOf(pos);
+                if (pos < result.size()) {
+                    result.set(pos, getBundlable(subKey, result.get(pos), factory));
+                } else {
+                    result.add(getBundlable(String.valueOf(pos),
+                            null /* force the creation of a new instance */,
+                            factory));
+                }
+            }
+            return result;
+        });
+    }
+
+    /**
+     * Returns the value associated with the given key, or an empty list if no mapping of the
+     * desired type exists for the given key. If a non-null "current" list is provided, then the
+     * deserialization would be done in place. Otherwise, a new list will be created and items will
+     * be instantiated using the provided factory.
+     *
+     * @param key lower camel case alphanumerical identifier
+     * @param current current value (if available) to perform in-place deserialization, or null
+     * @param factory a {@link Supplier} capable of providing an instance of a {@link Bundlable} of
+     *                type T. The suggested implementation is to pass a reference to the default
+     *                constructor of that class.
+     * @param <T> {@link Bundlable} type to be returned.
+     * @return a list of instances of type T, or an empty list. The resulting list might contain
+     *         null elements.
+     */
+    @NonNull
+    public <T extends Bundlable> List<T> getBundlableListNonNull(@NonNull String key,
+            @NonNull List<T> current, @NonNull Supplier<T> factory) {
+        List<T> result = getBundlableList(key, current, factory);
+        return result != null ? result : new ArrayList<>();
+    }
+
+    /**
+     * Executes the given {@link Runnable} in a context where {@link #getMangledKey(String)}
+     * includes the given key as part of the prefix. Calls to this method can be nested (the
+     * provided {@link Runnable} can call to this method if needed). This method should be used when
+     * serializing or deserializing nested objects.
+     * <p>
+     * For example: calling to {@link #withKeyPrefix(String, Runnable)} with "foo" as key and
+     * a {@link Runnable} that calls {@link #getMangledKey(String)} with "bar" as key, will
+     * cause such {@link #getMangledKey(String)} call to return "foo.bar".
+     */
+    private void withKeyPrefix(@NonNull String key, @NonNull Runnable runnable) {
+        String originalKeyPrefix = mKeyPrefix;
+        mKeyPrefix = mKeyPrefix + key + KEY_SEPARATOR;
+        runnable.run();
+        mKeyPrefix = originalKeyPrefix;
+    }
+
+    /**
+     * Similar to {@link #withKeyPrefix(String, Runnable)} but allows returning a value.
+     */
+    private <X> X withKeyPrefix(@NonNull String key, @NonNull Supplier<X> supplier) {
+        String originalKeyPrefix = mKeyPrefix;
+        mKeyPrefix = mKeyPrefix + key + KEY_SEPARATOR;
+        X res = supplier.get();
+        mKeyPrefix = originalKeyPrefix;
+        return res;
+    }
+
+    /**
+     * Returns a composed key based on the given one and the current serialization/deserialization
+     * key prefix (initially empty). This prefix can be temporarily changed with
+     * {@link #withKeyPrefix(String, Runnable)} or {@link #withKeyPrefix(String, Supplier)}.
+     */
+    private String getMangledKey(@NonNull String key) {
+        return mKeyPrefix + key;
+    }
+}
diff --git a/jetifier/jetifier/source-transformer/rewriteMake.py b/jetifier/jetifier/source-transformer/rewriteMake.py
index b33d4c1..a8ee1d7 100755
--- a/jetifier/jetifier/source-transformer/rewriteMake.py
+++ b/jetifier/jetifier/source-transformer/rewriteMake.py
@@ -88,6 +88,7 @@
 android-arch-room-migration,androidx.room_room-migration
 android-arch-room-runtime,androidx.room_room-runtime
 android-arch-room-testing,androidx.room_room-testing
+android-support-design,com.google.android.material_material
 $(ANDROID_SUPPORT_DESIGN_TARGETS),com.google.android.material_material"""
 
 reader = csv.reader(target_map.split('\n'), delimiter=',')
diff --git a/leanback/src/main/res/values-bn/strings.xml b/leanback/src/main/res/values-bn/strings.xml
index c7c7bed..a7ae625 100644
--- a/leanback/src/main/res/values-bn/strings.xml
+++ b/leanback/src/main/res/values-bn/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="lb_navigation_menu_contentDescription" msgid="8126335323963415494">"নেভিগেশন মেনু"</string>
-    <string name="orb_search_action" msgid="7534843523462177008">"খোঁজার কার্যকলাপ"</string>
+    <string name="orb_search_action" msgid="7534843523462177008">"খোঁজার অ্যাক্টিভিটি"</string>
     <string name="lb_search_bar_hint" msgid="4819380969103509861">"সার্চ"</string>
     <string name="lb_search_bar_hint_speech" msgid="2795474673510974502">"বলার মাধ্যমে খুঁজুন"</string>
     <string name="lb_search_bar_hint_with_title" msgid="7453744869467668159">"<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g> খুঁজুন"</string>
@@ -33,7 +33,7 @@
     <string name="lb_playback_controls_rewind_multiplier" msgid="8651612807713092781">"%1$dX স্পিডে পিছিয়ে যান"</string>
     <string name="lb_playback_controls_skip_next" msgid="4877009494447817003">"সরাসরি পরেরটিতে চলে যান"</string>
     <string name="lb_playback_controls_skip_previous" msgid="3147124289285911980">"সরাসরি আগেরটিতে চলে যান"</string>
-    <string name="lb_playback_controls_more_actions" msgid="2827883329510404797">"আরও কার্যকলাপ"</string>
+    <string name="lb_playback_controls_more_actions" msgid="2827883329510404797">"আরও অ্যাক্টিভিটি"</string>
     <string name="lb_playback_controls_thumb_up" msgid="8332816524260995892">"উপরের দিকে করা বুড়ো আঙ্গুলের চিহ্নকে বাদ দিন"</string>
     <string name="lb_playback_controls_thumb_up_outline" msgid="1038344559734334272">"উপরের দিকে করা বুড়ো আঙ্গুলের চিহ্নকে বেছে নিন"</string>
     <string name="lb_playback_controls_thumb_down" msgid="5075744418630733006">"নিচের দিকে করা বুড়ো আঙ্গুলের চিহ্নকে বাদ দিন"</string>
diff --git a/samples/SupportPreferenceDemos/build.gradle b/samples/SupportPreferenceDemos/build.gradle
index d37bfa2..6aa8343 100644
--- a/samples/SupportPreferenceDemos/build.gradle
+++ b/samples/SupportPreferenceDemos/build.gradle
@@ -8,4 +8,5 @@
     implementation(project(":preference"))
     implementation(project(":leanback"))
     implementation(project(":leanback-preference"))
+    implementation(project(":car"))
 }
diff --git a/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml b/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml
index 0e9aa9b..dc7f3dc 100644
--- a/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml
+++ b/samples/SupportPreferenceDemos/src/main/AndroidManifest.xml
@@ -20,7 +20,7 @@
     package="com.example.android.supportpreference">
 
     <uses-sdk
-        tools:overrideLibrary="androidx.leanback.preference, androidx.leanback" />
+        tools:overrideLibrary="androidx.leanback.preference, androidx.leanback, androidx.car" />
 
     <uses-feature android:name="android.software.Leanback" android:required="false" />
 
@@ -68,5 +68,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".FragmentSupportPreferencesCar"
+            android:label="@string/fragment_support_preferences_car_demo"
+            android:theme="@style/SupportPreferenceCar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="com.example.android.supportpreference.SAMPLE_CODE"/>
+            </intent-filter>
+        </activity>
+
     </application>
-</manifest>
+</manifest>
\ No newline at end of file
diff --git a/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCar.java b/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCar.java
new file mode 100644
index 0000000..83cb384
--- /dev/null
+++ b/samples/SupportPreferenceDemos/src/main/java/com/example/android/supportpreference/FragmentSupportPreferencesCar.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 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 com.example.android.supportpreference;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceScreen;
+
+/**
+ * Demonstration of PreferenceFragment, showing a single fragment in an
+ * activity for the Car.
+ */
+public class FragmentSupportPreferencesCar extends AppCompatActivity
+        implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Display the fragment as the main content.
+        if (savedInstanceState == null) {
+            getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+                    new PrefsFragment()).commitNow();
+        }
+    }
+
+    @Override
+    public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen pref) {
+        Fragment fragment = new PrefsFragment();
+        Bundle args = new Bundle();
+        args.putString(PreferenceFragmentCompat.ARG_PREFERENCE_ROOT, pref.getKey());
+        fragment.setArguments(args);
+        getSupportFragmentManager().beginTransaction()
+                .replace(android.R.id.content, fragment)
+                .commitNow();
+        return true;
+    }
+
+    /**
+     * Create a PrefsFragment from the xml file of preferences
+     */
+    public static class PrefsFragment extends PreferenceFragmentCompat {
+
+        @Override
+        public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+            // Load the preferences from an XML resource
+            setPreferencesFromResource(R.xml.preferences, rootKey);
+        }
+    }
+}
+
diff --git a/samples/SupportPreferenceDemos/src/main/res/values/strings.xml b/samples/SupportPreferenceDemos/src/main/res/values/strings.xml
index e6effdf..a3a81b1 100644
--- a/samples/SupportPreferenceDemos/src/main/res/values/strings.xml
+++ b/samples/SupportPreferenceDemos/src/main/res/values/strings.xml
@@ -19,6 +19,7 @@
     <string name="fragment_support_preferences_demo">Support PreferenceFragment</string>
     <string name="fragment_support_preferences_compat_demo">Support PreferenceFragmentCompat</string>
     <string name="fragment_support_preferences_leanback_demo">Support LeanbackPreferenceFragment</string>
+    <string name="fragment_support_preferences_car_demo">Support Car PreferenceFragment</string>
 
     <string name="root_title">Demo Preferences</string>
 
diff --git a/samples/SupportPreferenceDemos/src/main/res/values/styles.xml b/samples/SupportPreferenceDemos/src/main/res/values/styles.xml
index 5194c9f..6b28378 100644
--- a/samples/SupportPreferenceDemos/src/main/res/values/styles.xml
+++ b/samples/SupportPreferenceDemos/src/main/res/values/styles.xml
@@ -31,4 +31,9 @@
         <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Leanback</item>
     </style>
 
+    <style name="SupportPreferenceCar" parent="Theme.Car.NoActionBar">
+        <item name="preferenceTheme">@style/PreferenceThemeOverlayCar</item>
+        <item name="android:windowBackground">@color/car_card</item>
+    </style>
+
 </resources>
diff --git a/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml b/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml
index 4d1dca7..2f49202 100644
--- a/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml
+++ b/samples/SupportPreferenceDemos/src/main/res/xml/preferences.xml
@@ -57,6 +57,13 @@
             android:entries="@array/entries_list_preference"
             android:entryValues="@array/entryvalues_list_preference" />
 
+        <SeekBarPreference
+            android:key="seekbar_preference"
+            android:title="Seekbar preference"
+            android:summary="This is a seekbar preference"
+            android:max="10"
+            android:defaultValue="5"/>
+
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index 7c51a4b..1b84be5 100644
--- a/work/integration-tests/testapp/build.gradle
+++ b/work/integration-tests/testapp/build.gradle
@@ -37,8 +37,8 @@
     implementation project(':work:work-runtime')
     implementation project(':work:work-firebase')
     implementation "android.arch.lifecycle:extensions:1.1.0"
-    implementation "android.arch.persistence.room:runtime:1.1.0"
-    annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
+    implementation "android.arch.persistence.room:runtime:1.1.1-rc1"
+    annotationProcessor "android.arch.persistence.room:compiler:1.1.1-rc1"
     implementation MULTIDEX
     implementation "com.android.support:recyclerview-v7:26.1.0"
     implementation "com.android.support:appcompat-v7:26.1.0"
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java
index 37cd7fc..9f117ec 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/ToastWorker.java
@@ -45,12 +45,16 @@
     @Override
     public @NonNull Result doWork() {
         Data input = getInputData();
-        final String message = input.getString(ARG_MESSAGE, "completed!");
+        String message = input.getString(ARG_MESSAGE);
+        if (message == null) {
+            message = "completed!";
+        }
+        final String displayMessage = message;
         new Handler(Looper.getMainLooper()).post(new Runnable() {
             @Override
             public void run() {
-                Log.d("ToastWorker", message);
-                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
+                Log.d("ToastWorker", displayMessage);
+                Toast.makeText(getApplicationContext(), displayMessage, Toast.LENGTH_SHORT).show();
             }
         });
         return Result.SUCCESS;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java
index 0fa1c5b..e28d942 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageProcessingWorker.java
@@ -47,7 +47,7 @@
     public @NonNull Result doWork() {
         Log.d(TAG, "Started");
 
-        String uriString = getInputData().getString(URI_KEY, null);
+        String uriString = getInputData().getString(URI_KEY);
         if (TextUtils.isEmpty(uriString)) {
             Log.e(TAG, "Invalid URI!");
             return Result.FAILURE;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java
index 304e7fe..1d3520f 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageSetupWorker.java
@@ -37,7 +37,7 @@
     public @NonNull Result doWork() {
         Log.d(TAG, "Started");
 
-        String uriString = getInputData().getString(URI_KEY, null);
+        String uriString = getInputData().getString(URI_KEY);
         if (TextUtils.isEmpty(uriString)) {
             Log.e(TAG, "Invalid URI!");
             return Result.FAILURE;
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java
index fcc7540..2fca443 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextMappingWorker.java
@@ -57,7 +57,7 @@
     @Override
     public @NonNull Result doWork() {
         Data input = getInputData();
-        String inputFileName = input.getString(INPUT_FILE, null);
+        String inputFileName = input.getString(INPUT_FILE);
         String outputFileName = "out_" + inputFileName;
 
         AssetManager assetManager = getApplicationContext().getAssets();
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/DataTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/DataTest.kt
index 20b19f6..0cd1eda 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/DataTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/DataTest.kt
@@ -32,10 +32,10 @@
         val data = map.toWorkData()
         assertEquals(data.getInt("one", 0), 1)
         assertEquals(data.getLong("two", 0L), 2L)
-        assertEquals(data.getString("three", null), "Three")
+        assertEquals(data.getString("three"), "Three")
         val longArray = data.getLongArray("four")
         assertNotNull(longArray)
-        assertEquals(longArray.size, 2)
+        assertEquals(longArray!!.size, 2)
         assertEquals(longArray[0], 1L)
         assertEquals(longArray[1], 2L)
     }
diff --git a/work/workmanager/build.gradle b/work/workmanager/build.gradle
index fedc052..172c5ff 100644
--- a/work/workmanager/build.gradle
+++ b/work/workmanager/build.gradle
@@ -43,10 +43,10 @@
 
 dependencies {
     api "android.arch.lifecycle:extensions:1.1.0"
-    implementation "android.arch.persistence.room:runtime:1.1.0"
-    annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
+    implementation "android.arch.persistence.room:runtime:1.1.1-rc1"
+    annotationProcessor "android.arch.persistence.room:compiler:1.1.1-rc1"
     androidTestImplementation "android.arch.core:core-testing:1.1.0"
-    androidTestImplementation "android.arch.persistence.room:testing:1.1.0"
+    androidTestImplementation "android.arch.persistence.room:testing:1.1.1-rc1"
     androidTestImplementation(TEST_RUNNER)
     androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index c131117..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;
 
@@ -25,16 +31,21 @@
 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.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;
 import androidx.work.worker.TestWorker;
 
 import org.junit.Before;
@@ -51,8 +62,6 @@
 
     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 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 +78,7 @@
     private static final String TABLE_WORKTAG = "WorkTag";
     private static final String TABLE_WORKNAME = "WorkName";
 
+    private Context mContext;
     private File mDatabasePath;
 
     @Rule
@@ -80,6 +90,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,11 +101,143 @@
     @MediumTest
     public void testMigrationVersion1To2() throws IOException {
         SupportSQLiteDatabase database =
-                mMigrationTestHelper.createDatabase(TEST_DATABASE, OLD_VERSION);
+                mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_1);
 
-        String workSpecId0 = UUID.randomUUID().toString();
+        String[] prepopulatedWorkSpecIds = new String[]{
+                UUID.randomUUID().toString(),
+                UUID.randomUUID().toString()
+        };
+        for (String workSpecId : prepopulatedWorkSpecIds) {
+            ContentValues contentValues = contentValues(workSpecId);
+            database.insert("workspec", CONFLICT_FAIL, contentValues);
+
+            if (workSpecId.equals(prepopulatedWorkSpecIds[0])) {
+                ContentValues tagValues = new ContentValues();
+                tagValues.put("tag", TestWorker.class.getName());
+                tagValues.put("work_spec_id", workSpecId);
+                database.insert("worktag", CONFLICT_FAIL, tagValues);
+            }
+        }
+
+        String workSpecId1 = UUID.randomUUID().toString();
+        String workSpecId2 = UUID.randomUUID().toString();
+
+        // insert alarmInfos
+        database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId1, 1});
+        database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId2, 2});
+
+        database.close();
+
+        database = mMigrationTestHelper.runMigrationsAndValidate(
+                TEST_DATABASE,
+                VERSION_2,
+                VALIDATE_DROPPED_TABLES,
+                WorkDatabaseMigrations.MIGRATION_1_2);
+
+        Cursor tagCursor = database.query("SELECT * FROM worktag");
+        assertThat(tagCursor.getCount(), is(prepopulatedWorkSpecIds.length));
+        boolean[] foundWorkSpecId = new boolean[prepopulatedWorkSpecIds.length];
+        for (int i = 0; i < prepopulatedWorkSpecIds.length; ++i) {
+            tagCursor.moveToPosition(i);
+            assertThat(tagCursor.getString(tagCursor.getColumnIndex("tag")),
+                    is(TestWorker.class.getName()));
+            String currentId = tagCursor.getString(tagCursor.getColumnIndex("work_spec_id"));
+            for (int j = 0; j < prepopulatedWorkSpecIds.length; ++j) {
+                if (prepopulatedWorkSpecIds[j].equals(currentId)) {
+                    foundWorkSpecId[j] = true;
+                    break;
+                }
+            }
+        }
+        for (int i = 0; i < prepopulatedWorkSpecIds.length; ++i) {
+            assertThat(foundWorkSpecId[i], is(true));
+        }
+        tagCursor.close();
+
+        Cursor cursor = database.query(CHECK_SYSTEM_ID_INFO);
+        assertThat(cursor.getCount(), is(2));
+        cursor.moveToFirst();
+        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId1));
+        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_SYSTEM_ID)), is(1));
+        cursor.moveToNext();
+        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId2));
+        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_SYSTEM_ID)), is(2));
+        cursor.close();
+
+        assertThat(checkExists(database, TABLE_ALARM_INFO), is(false));
+        assertThat(checkExists(database, TABLE_WORKSPEC), is(true));
+        assertThat(checkExists(database, TABLE_WORKTAG), is(true));
+        assertThat(checkExists(database, TABLE_WORKNAME), is(true));
+        database.close();
+    }
+
+    @Test
+    @MediumTest
+    public void testMigrationVersion2To3() throws IOException {
+        SupportSQLiteDatabase database =
+                mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_2);
+        WorkDatabaseMigrations.WorkMigration migration2To3 =
+                new WorkDatabaseMigrations.WorkMigration(mContext, VERSION_2, VERSION_3);
+
+        database = mMigrationTestHelper.runMigrationsAndValidate(
+                TEST_DATABASE,
+                VERSION_3,
+                VALIDATE_DROPPED_TABLES,
+                migration2To3);
+
+        Preferences preferences = new Preferences(mContext);
+        assertThat(preferences.needsReschedule(), is(true));
+        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", workSpecId0);
+        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());
@@ -117,131 +260,7 @@
         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();
-
-        // insert alarmInfos
-        database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId1, 1});
-        database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId2, 2});
-
-        database.close();
-
-        database = mMigrationTestHelper.runMigrationsAndValidate(
-                TEST_DATABASE,
-                NEW_VERSION,
-                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();
-        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId1));
-        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_SYSTEM_ID)), is(1));
-        cursor.moveToNext();
-        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId2));
-        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_SYSTEM_ID)), is(2));
-        cursor.close();
-
-        assertThat(checkExists(database, TABLE_ALARM_INFO), is(false));
-        assertThat(checkExists(database, TABLE_WORKSPEC), is(true));
-        assertThat(checkExists(database, TABLE_WORKTAG), is(true));
-        assertThat(checkExists(database, TABLE_WORKNAME), is(true));
-        database.close();
-    }
-
-    @Test
-    @MediumTest
-    public void testMigrationVersion2To1() throws IOException {
-        SupportSQLiteDatabase database =
-                mMigrationTestHelper.createDatabase(TEST_DATABASE, NEW_VERSION);
-
-        String workSpecId1 = UUID.randomUUID().toString();
-        String workSpecId2 = UUID.randomUUID().toString();
-
-        // insert SystemIdInfo
-        database.execSQL(INSERT_SYSTEM_ID_INFO, new Object[]{workSpecId1, 1});
-        database.execSQL(INSERT_SYSTEM_ID_INFO, new Object[]{workSpecId2, 2});
-
-        database.close();
-
-
-        database = mMigrationTestHelper.runMigrationsAndValidate(
-                TEST_DATABASE,
-                OLD_VERSION,
-                VALIDATE_DROPPED_TABLES,
-                WorkDatabaseMigrations.MIGRATION_2_1);
-
-        Cursor cursor = database.query(CHECK_ALARM_INFO);
-        assertThat(cursor.getCount(), is(2));
-        cursor.moveToFirst();
-        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId1));
-        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_ALARM_ID)), is(1));
-        cursor.moveToNext();
-        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId2));
-        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_ALARM_ID)), is(2));
-        cursor.close();
-
-        assertThat(checkExists(database, TABLE_SYSTEM_ID_INFO), is(false));
-        assertThat(checkExists(database, TABLE_WORKSPEC), is(true));
-        assertThat(checkExists(database, TABLE_WORKTAG), is(true));
-        assertThat(checkExists(database, TABLE_WORKNAME), is(true));
-        database.close();
-    }
-
-    @Test
-    @MediumTest
-    public void testMigrationVersion1To2To1() throws IOException {
-        SupportSQLiteDatabase database =
-                mMigrationTestHelper.createDatabase(TEST_DATABASE, OLD_VERSION);
-
-        String workSpecId1 = UUID.randomUUID().toString();
-        String workSpecId2 = UUID.randomUUID().toString();
-
-        // insert alarmInfos
-        database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId1, 1});
-        database.execSQL(INSERT_ALARM_INFO, new Object[]{workSpecId2, 2});
-
-        database.close();
-
-        database = mMigrationTestHelper.runMigrationsAndValidate(
-                TEST_DATABASE,
-                NEW_VERSION,
-                VALIDATE_DROPPED_TABLES,
-                WorkDatabaseMigrations.MIGRATION_1_2);
-
-        database.close();
-
-        database = mMigrationTestHelper.runMigrationsAndValidate(
-                TEST_DATABASE,
-                OLD_VERSION,
-                VALIDATE_DROPPED_TABLES,
-                WorkDatabaseMigrations.MIGRATION_2_1);
-
-        Cursor cursor = database.query(CHECK_ALARM_INFO);
-        assertThat(cursor.getCount(), is(2));
-        cursor.moveToFirst();
-        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId1));
-        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_ALARM_ID)), is(1));
-        cursor.moveToNext();
-        assertThat(cursor.getString(cursor.getColumnIndex(COLUMN_WORKSPEC_ID)), is(workSpecId2));
-        assertThat(cursor.getInt(cursor.getColumnIndex(COLUMN_ALARM_ID)), is(2));
-        cursor.close();
-
-        assertThat(checkExists(database, TABLE_SYSTEM_ID_INFO), is(false));
-        assertThat(checkExists(database, TABLE_WORKSPEC), is(true));
-        assertThat(checkExists(database, TABLE_WORKTAG), is(true));
-        assertThat(checkExists(database, TABLE_WORKNAME), is(true));
-        database.close();
+        return contentValues;
     }
 
     private boolean checkExists(SupportSQLiteDatabase database, String tableName) {
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 72ebe94..23996eb 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -18,6 +18,7 @@
 
 import static androidx.work.worker.CheckLimitsWorker.KEY_EXCEEDS_SCHEDULER_LIMIT;
 import static androidx.work.worker.CheckLimitsWorker.KEY_LIMIT_TO_ENFORCE;
+import static androidx.work.worker.CheckLimitsWorker.KEY_RECURSIVE;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -65,8 +66,7 @@
 @RunWith(AndroidJUnit4.class)
 public class WorkManagerImplLargeExecutorTest {
 
-    private static final int NUM_WORKERS = 500;
-    private static final int TEST_TIMEOUT_SECONDS = 30;
+    private static final int NUM_WORKERS = 200;
 
     // ThreadPoolExecutor parameters.
     private static final int MIN_POOL_SIZE = 0;
@@ -172,7 +172,66 @@
                 });
 
         continuation.enqueue();
-        latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        latch.await(120L, TimeUnit.SECONDS);
+        assertThat(latch.getCount(), is(0L));
+    }
+
+    @Test
+    @LargeTest
+    @SdkSuppress(maxSdkVersion = 22)
+    public void testSchedulerLimitsRecursive() throws InterruptedException {
+        List<OneTimeWorkRequest> workRequests = new ArrayList<>(NUM_WORKERS);
+        final Set<UUID> completed = new HashSet<>(NUM_WORKERS);
+        final int schedulerLimit = mWorkManagerImpl
+                .getConfiguration()
+                .getMaxSchedulerLimit();
+
+        final Data input = new Data.Builder()
+                .putBoolean(KEY_RECURSIVE, true)
+                .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 * 2); // recursive
+        WorkContinuation continuation = mWorkManagerImpl.beginWith(workRequests);
+
+        // There are more workers being enqueued recursively so use implicit tags.
+        mWorkManagerImpl.getStatusesByTag(CheckLimitsWorker.class.getName())
+                .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(240L, 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 3e01164..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,6 +1406,7 @@
         WorkDatabase.generateCleanupCallback().onOpen(db);
 
         assertThat(workSpecDao.getState(work.getStringId()), is(ENQUEUED));
+        assertThat(work.getWorkSpec().scheduleRequestedAt, is(SCHEDULE_NOT_REQUESTED_YET));
     }
 
     @Test
@@ -1508,7 +1510,7 @@
         WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
         assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
         assertThat(workSpec.input.getString(
-                ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, null),
+                ConstraintTrackingWorker.ARGUMENT_CLASS_NAME),
                 is(TestWorker.class.getName()));
     }
 
@@ -1526,7 +1528,7 @@
         WorkSpec workSpec = mDatabase.workSpecDao().getWorkSpec(work.getStringId());
         assertThat(workSpec.workerClassName, is(ConstraintTrackingWorker.class.getName()));
         assertThat(workSpec.input.getString(
-                ConstraintTrackingWorker.ARGUMENT_CLASS_NAME, null),
+                ConstraintTrackingWorker.ARGUMENT_CLASS_NAME),
                 is(TestWorker.class.getName()));
     }
 
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..44e65f8 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,15 @@
         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
@@ -563,16 +575,34 @@
     @Test
     @SmallTest
     public void testScheduler() {
-        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
-        insertWork(work);
-        Scheduler mockScheduler = mock(Scheduler.class);
+        OneTimeWorkRequest prerequisiteWork =
+                new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class)
+                .setInitialState(BLOCKED).build();
+        Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId());
 
-        new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, work.getStringId())
-                .withSchedulers(Collections.singletonList(mockScheduler))
+        mDatabase.beginTransaction();
+        try {
+            insertWork(prerequisiteWork);
+            insertWork(work);
+            mDependencyDao.insertDependency(dependency);
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        new WorkerWrapper.Builder(
+                mContext,
+                mConfiguration,
+                mDatabase,
+                prerequisiteWork.getStringId())
+                .withSchedulers(Collections.singletonList(mMockScheduler))
                 .build()
                 .run();
 
-        verify(mockScheduler).schedule();
+        ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
+        verify(mMockScheduler).schedule(captor.capture());
+        assertThat(captor.getValue().id, is(work.getStringId()));
     }
 
     @Test
@@ -603,7 +633,7 @@
                 new Extras(input, Collections.<String>emptyList(), null, 1));
 
         assertThat(worker, is(notNullValue()));
-        assertThat(worker.getInputData().getString(key, null), is(expectedValue));
+        assertThat(worker.getInputData().getString(key), is(expectedValue));
 
         work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         worker = WorkerWrapper.workerFromWorkSpec(
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/background/systemalarm/WorkTimerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
index 78986c3..700071a 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
@@ -49,7 +49,7 @@
     public void testTimer_withListenerAndCleanUp() throws InterruptedException {
         TestTimeLimitExceededListener listenerSpy = spy(mListener);
         mWorkTimer.startTimer(WORKSPEC_ID_1, 0, listenerSpy);
-        Thread.sleep(10); // introduce a small delay
+        Thread.sleep(100); // introduce a small delay
         verify(listenerSpy, times(1)).onTimeLimitExceeded(WORKSPEC_ID_1);
         assertThat(mWorkTimer.getTimerMap().size(), is(0));
         assertThat(mWorkTimer.getListeners().size(), is(0));
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 323757a..3d6b4c0 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
@@ -18,7 +18,6 @@
 
 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;
@@ -76,37 +75,28 @@
     @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();
+        verify(mWorkManager, times(1)).onForceStopRunnableCompleted();
     }
 
     @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();
+        verify(mWorkManager, times(1)).onForceStopRunnableCompleted();
     }
 
     @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();
+        verify(mPreferences, times(1)).setNeedsReschedule(false);
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java
index 1176364..f0fb9a1 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/CheckLimitsWorker.java
@@ -21,6 +21,7 @@
 import android.support.annotation.NonNull;
 
 import androidx.work.Data;
+import androidx.work.OneTimeWorkRequest;
 import androidx.work.Worker;
 import androidx.work.impl.Scheduler;
 import androidx.work.impl.WorkManagerImpl;
@@ -30,6 +31,7 @@
 
 public class CheckLimitsWorker extends Worker {
     /* The limit to enforce */
+    public static final String KEY_RECURSIVE = "recursive";
     public static final String KEY_LIMIT_TO_ENFORCE = "limit";
 
     /* The output key which tells us if we exceeded the scheduler limits. */
@@ -39,6 +41,7 @@
     @Override
     public Result doWork() {
         Data input = getInputData();
+        boolean isRecursive = input.getBoolean(KEY_RECURSIVE, false);
         int limitToEnforce = input.getInt(KEY_LIMIT_TO_ENFORCE, Scheduler.MAX_SCHEDULER_LIMIT);
         WorkManagerImpl workManager = WorkManagerImpl.getInstance();
         List<WorkSpec> eligibleWorkSpecs = workManager.getWorkDatabase()
@@ -49,8 +52,19 @@
         Data output = new Data.Builder()
                 .putBoolean(KEY_EXCEEDS_SCHEDULER_LIMIT, exceedsLimits)
                 .build();
-
         setOutputData(output);
+        if (isRecursive) {
+            // kick off another Worker, which is not recursive.
+            Data newRequestData = new Data.Builder()
+                    .putAll(getInputData())
+                    .putBoolean(KEY_RECURSIVE, false)
+                    .build();
+
+            OneTimeWorkRequest newRequest = new OneTimeWorkRequest.Builder(CheckLimitsWorker.class)
+                    .setInputData(newRequestData)
+                    .build();
+            workManager.enqueue(newRequest);
+        }
         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 77f5ffa..a74c4b5 100644
--- a/work/workmanager/src/main/java/androidx/work/Configuration.java
+++ b/work/workmanager/src/main/java/androidx/work/Configuration.java
@@ -103,7 +103,7 @@
         }
     }
 
-    private Executor createDefaultExecutor() {
+    private @NonNull Executor createDefaultExecutor() {
         return Executors.newFixedThreadPool(
                 // This value is the same as the core pool size for AsyncTask#THREAD_POOL_EXECUTOR.
                 Math.max(2, Math.min(Runtime.getRuntime().availableProcessors() - 1, 4)));
@@ -125,7 +125,7 @@
          * @param executor An {@link Executor} for processing work
          * @return This {@link Builder} instance
          */
-        public Builder setExecutor(@NonNull Executor executor) {
+        public @NonNull Builder setExecutor(@NonNull Executor executor) {
             mExecutor = executor;
             return this;
         }
@@ -139,7 +139,9 @@
          * @return This {@link Builder} instance
          * @throws IllegalArgumentException when the size of the range is < 1000
          */
-        public Builder setJobSchedulerJobIdRange(int minJobSchedulerId, int maxJobSchedulerId) {
+        public @NonNull Builder setJobSchedulerJobIdRange(
+                int minJobSchedulerId,
+                int maxJobSchedulerId) {
             if ((maxJobSchedulerId - minJobSchedulerId) < 1000) {
                 throw new IllegalArgumentException(
                         "WorkManager needs a range of at least 1000 job ids.");
@@ -167,7 +169,7 @@
          * @throws IllegalArgumentException when the number of jobs <
          *                                  {@link Configuration#MIN_SCHEDULER_LIMIT}
          */
-        public Builder setMaxSchedulerLimit(int maxSchedulerLimit) {
+        public @NonNull Builder setMaxSchedulerLimit(int maxSchedulerLimit) {
             if (maxSchedulerLimit < MIN_SCHEDULER_LIMIT) {
                 throw new IllegalArgumentException(
                         "WorkManager needs to be able to schedule at least 20 jobs in "
@@ -185,7 +187,7 @@
          * @deprecated Use the {@link Configuration.Builder#setExecutor(Executor)} method instead
          */
         @Deprecated
-        public Builder withExecutor(@NonNull Executor executor) {
+        public @NonNull Builder withExecutor(@NonNull Executor executor) {
             mExecutor = executor;
             return this;
         }
@@ -195,7 +197,7 @@
          *
          * @return A {@link Configuration} object with this {@link Builder}'s parameters.
          */
-        public Configuration build() {
+        public @NonNull Configuration build() {
             return new Configuration(this);
         }
     }
diff --git a/work/workmanager/src/main/java/androidx/work/Constraints.java b/work/workmanager/src/main/java/androidx/work/Constraints.java
index d6d6ad6..8b8a740 100644
--- a/work/workmanager/src/main/java/androidx/work/Constraints.java
+++ b/work/workmanager/src/main/java/androidx/work/Constraints.java
@@ -20,6 +20,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.RequiresApi;
 
 /**
@@ -29,23 +30,24 @@
 
     public static final Constraints NONE = new Constraints.Builder().build();
 
+    // TODO(sumir): Need to make this @NonNull, but that requires a db migration.
     @ColumnInfo(name = "required_network_type")
-    NetworkType mRequiredNetworkType;
+    private NetworkType mRequiredNetworkType;
 
     @ColumnInfo(name = "requires_charging")
-    boolean mRequiresCharging;
+    private boolean mRequiresCharging;
 
     @ColumnInfo(name = "requires_device_idle")
-    boolean mRequiresDeviceIdle;
+    private boolean mRequiresDeviceIdle;
 
     @ColumnInfo(name = "requires_battery_not_low")
-    boolean mRequiresBatteryNotLow;
+    private boolean mRequiresBatteryNotLow;
 
     @ColumnInfo(name = "requires_storage_not_low")
-    boolean mRequiresStorageNotLow;
+    private boolean mRequiresStorageNotLow;
 
     @ColumnInfo(name = "content_uri_triggers")
-    ContentUriTriggers mContentUriTriggers;
+    private @Nullable ContentUriTriggers mContentUriTriggers;
 
     public Constraints() { // stub required for room
     }
@@ -116,12 +118,12 @@
     }
 
     @RequiresApi(24)
-    public void setContentUriTriggers(ContentUriTriggers mContentUriTriggers) {
+    public void setContentUriTriggers(@Nullable ContentUriTriggers mContentUriTriggers) {
         this.mContentUriTriggers = mContentUriTriggers;
     }
 
     @RequiresApi(24)
-    public ContentUriTriggers getContentUriTriggers() {
+    public @Nullable ContentUriTriggers getContentUriTriggers() {
         return mContentUriTriggers;
     }
 
@@ -130,7 +132,7 @@
      */
     @RequiresApi(24)
     public boolean hasContentUriTriggers() {
-        return mContentUriTriggers.size() > 0;
+        return mContentUriTriggers != null && mContentUriTriggers.size() > 0;
     }
 
     @Override
@@ -180,7 +182,7 @@
          * @param requiresCharging true if device must be plugged in, false otherwise
          * @return current builder
          */
-        public Builder setRequiresCharging(boolean requiresCharging) {
+        public @NonNull Builder setRequiresCharging(boolean requiresCharging) {
             this.mRequiresCharging = requiresCharging;
             return this;
         }
@@ -193,7 +195,7 @@
          * @return current builder
          */
         @RequiresApi(23)
-        public Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
+        public @NonNull Builder setRequiresDeviceIdle(boolean requiresDeviceIdle) {
             this.mRequiresDeviceIdle = requiresDeviceIdle;
             return this;
         }
@@ -205,7 +207,7 @@
          * @param networkType type of network required
          * @return current builder
          */
-        public Builder setRequiredNetworkType(@NonNull NetworkType networkType) {
+        public @NonNull Builder setRequiredNetworkType(@NonNull NetworkType networkType) {
             this.mRequiredNetworkType = networkType;
             return this;
         }
@@ -218,7 +220,7 @@
          *                              false otherwise
          * @return current builder
          */
-        public Builder setRequiresBatteryNotLow(boolean requiresBatteryNotLow) {
+        public @NonNull Builder setRequiresBatteryNotLow(boolean requiresBatteryNotLow) {
             this.mRequiresBatteryNotLow = requiresBatteryNotLow;
             return this;
         }
@@ -231,7 +233,7 @@
          *                              threshold, false otherwise
          * @return current builder
          */
-        public Builder setRequiresStorageNotLow(boolean requiresStorageNotLow) {
+        public @NonNull Builder setRequiresStorageNotLow(boolean requiresStorageNotLow) {
             this.mRequiresStorageNotLow = requiresStorageNotLow;
             return this;
         }
@@ -246,7 +248,7 @@
          * @return The current {@link Builder}
          */
         @RequiresApi(24)
-        public Builder addContentUriTrigger(Uri uri, boolean triggerForDescendants) {
+        public @NonNull Builder addContentUriTrigger(Uri uri, boolean triggerForDescendants) {
             mContentUriTriggers.add(uri, triggerForDescendants);
             return this;
         }
@@ -256,7 +258,7 @@
          *
          * @return new {@link Constraints} which can be attached to a {@link WorkRequest}
          */
-        public Constraints build() {
+        public @NonNull Constraints build() {
             return new Constraints(this);
         }
     }
diff --git a/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java b/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java
index 0702cc0..ca6dd30 100644
--- a/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java
+++ b/work/workmanager/src/main/java/androidx/work/ContentUriTriggers.java
@@ -27,6 +27,7 @@
  * Stores a set of {@link Trigger}s
  */
 public final class ContentUriTriggers implements Iterable<ContentUriTriggers.Trigger> {
+
     private final Set<Trigger> mTriggers = new HashSet<>();
 
     /**
@@ -35,7 +36,7 @@
      * @param triggerForDescendants {@code true} if any changes in descendants cause this
      *                              {@link WorkRequest} to run
      */
-    public void add(Uri uri, boolean triggerForDescendants) {
+    public void add(@NonNull Uri uri, boolean triggerForDescendants) {
         Trigger trigger = new Trigger(uri, triggerForDescendants);
         mTriggers.add(trigger);
     }
@@ -73,18 +74,15 @@
      */
 
     public static final class Trigger {
-        @NonNull
-        private final Uri mUri;
+        private final @NonNull Uri mUri;
         private final boolean mTriggerForDescendants;
 
-        public Trigger(@NonNull Uri uri,
-                       boolean triggerForDescendants) {
+        Trigger(@NonNull Uri uri, boolean triggerForDescendants) {
             mUri = uri;
             mTriggerForDescendants = triggerForDescendants;
         }
 
-        @NonNull
-        public Uri getUri() {
+        public @NonNull Uri getUri() {
             return mUri;
         }
 
diff --git a/work/workmanager/src/main/java/androidx/work/Data.java b/work/workmanager/src/main/java/androidx/work/Data.java
index 376a00d..b687a36 100644
--- a/work/workmanager/src/main/java/androidx/work/Data.java
+++ b/work/workmanager/src/main/java/androidx/work/Data.java
@@ -18,6 +18,7 @@
 
 import android.arch.persistence.room.TypeConverter;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 
 import java.io.ByteArrayInputStream;
@@ -60,7 +61,7 @@
      * @param defaultValue The default value to return if the key is not found
      * @return The value specified by the key if it exists; the default value otherwise
      */
-    public boolean getBoolean(String key, boolean defaultValue) {
+    public boolean getBoolean(@NonNull String key, boolean defaultValue) {
         Object value = mValues.get(key);
         if (value instanceof Boolean) {
             return (boolean) value;
@@ -75,7 +76,7 @@
      * @param key The key for the argument
      * @return The value specified by the key if it exists; {@code null} otherwise
      */
-    public boolean[] getBooleanArray(String key) {
+    public @NonNull boolean[] getBooleanArray(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof Boolean[]) {
             Boolean[] array = (Boolean[]) value;
@@ -97,7 +98,7 @@
      * @param defaultValue The default value to return if the key is not found
      * @return The value specified by the key if it exists; the default value otherwise
      */
-    public int getInt(String key, int defaultValue) {
+    public int getInt(@NonNull String key, int defaultValue) {
         Object value = mValues.get(key);
         if (value instanceof Integer) {
             return (int) value;
@@ -112,7 +113,7 @@
      * @param key The key for the argument
      * @return The value specified by the key if it exists; {@code null} otherwise
      */
-    public int[] getIntArray(String key) {
+    public @NonNull int[] getIntArray(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof Integer[]) {
             Integer[] array = (Integer[]) value;
@@ -133,7 +134,7 @@
      * @param defaultValue The default value to return if the key is not found
      * @return The value specified by the key if it exists; the default value otherwise
      */
-    public long getLong(String key, long defaultValue) {
+    public long getLong(@NonNull String key, long defaultValue) {
         Object value = mValues.get(key);
         if (value instanceof Long) {
             return (long) value;
@@ -148,7 +149,7 @@
      * @param key The key for the argument
      * @return The value specified by the key if it exists; {@code null} otherwise
      */
-    public long[] getLongArray(String key) {
+    public @Nullable long[] getLongArray(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof Long[]) {
             Long[] array = (Long[]) value;
@@ -169,7 +170,7 @@
      * @param defaultValue The default value to return if the key is not found
      * @return The value specified by the key if it exists; the default value otherwise
      */
-    public float getFloat(String key, float defaultValue) {
+    public float getFloat(@NonNull String key, float defaultValue) {
         Object value = mValues.get(key);
         if (value instanceof Float) {
             return (float) value;
@@ -184,7 +185,7 @@
      * @param key The key for the argument
      * @return The value specified by the key if it exists; {@code null} otherwise
      */
-    public float[] getFloatArray(String key) {
+    public @Nullable float[] getFloatArray(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof Float[]) {
             Float[] array = (Float[]) value;
@@ -205,7 +206,7 @@
      * @param defaultValue The default value to return if the key is not found
      * @return The value specified by the key if it exists; the default value otherwise
      */
-    public double getDouble(String key, double defaultValue) {
+    public double getDouble(@NonNull String key, double defaultValue) {
         Object value = mValues.get(key);
         if (value instanceof Double) {
             return (double) value;
@@ -220,7 +221,7 @@
      * @param key The key for the argument
      * @return The value specified by the key if it exists; {@code null} otherwise
      */
-    public double[] getDoubleArray(String key) {
+    public @Nullable double[] getDoubleArray(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof Double[]) {
             Double[] array = (Double[]) value;
@@ -238,15 +239,14 @@
      * Get the String value for the given key.
      *
      * @param key The key for the argument
-     * @param defaultValue The default value to return if the key is not found
      * @return The value specified by the key if it exists; the default value otherwise
      */
-    public String getString(String key, String defaultValue) {
+    public @Nullable String getString(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof String) {
             return (String) value;
         } else {
-            return defaultValue;
+            return null;
         }
     }
 
@@ -256,7 +256,7 @@
      * @param key The key for the argument
      * @return The value specified by the key if it exists; {@code null} otherwise
      */
-    public String[] getStringArray(String key) {
+    public @Nullable String[] getStringArray(@NonNull String key) {
         Object value = mValues.get(key);
         if (value instanceof String[]) {
             return (String[]) value;
@@ -271,7 +271,7 @@
      * @return A {@link Map} of key-value pairs for this object; this Map is unmodifiable and should
      * be used for reads only.
      */
-    public Map<String, Object> getKeyValueMap() {
+    public @NonNull Map<String, Object> getKeyValueMap() {
         return Collections.unmodifiableMap(mValues);
     }
 
@@ -292,7 +292,7 @@
      *         {@link #MAX_DATA_BYTES}
      */
     @TypeConverter
-    public static byte[] toByteArray(Data data) throws IllegalStateException {
+    public static @NonNull byte[] toByteArray(@NonNull Data data) throws IllegalStateException {
         ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
         ObjectOutputStream objectOutputStream = null;
         try {
@@ -334,7 +334,7 @@
      * @throws IllegalStateException if bytes is bigger than {@link #MAX_DATA_BYTES}
      */
     @TypeConverter
-    public static Data fromByteArray(byte[] bytes) throws IllegalStateException {
+    public static @NonNull Data fromByteArray(@NonNull byte[] bytes) throws IllegalStateException {
         if (bytes.length > MAX_DATA_BYTES) {
             throw new IllegalStateException(
                     "Data cannot occupy more than " + MAX_DATA_BYTES + "KB when serialized");
@@ -438,7 +438,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putBoolean(String key, boolean value) {
+        public @NonNull Builder putBoolean(@NonNull String key, boolean value) {
             mValues.put(key, value);
             return this;
         }
@@ -450,7 +450,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putBooleanArray(String key, boolean[] value) {
+        public @NonNull Builder putBooleanArray(@NonNull String key, boolean[] value) {
             mValues.put(key, convertPrimitiveBooleanArray(value));
             return this;
         }
@@ -462,7 +462,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putInt(String key, int value) {
+        public @NonNull Builder putInt(@NonNull String key, int value) {
             mValues.put(key, value);
             return this;
         }
@@ -474,7 +474,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putIntArray(String key, int[] value) {
+        public @NonNull Builder putIntArray(@NonNull String key, int[] value) {
             mValues.put(key, convertPrimitiveIntArray(value));
             return this;
         }
@@ -486,7 +486,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putLong(String key, long value) {
+        public @NonNull Builder putLong(@NonNull String key, long value) {
             mValues.put(key, value);
             return this;
         }
@@ -498,7 +498,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putLongArray(String key, long[] value) {
+        public @NonNull Builder putLongArray(@NonNull String key, long[] value) {
             mValues.put(key, convertPrimitiveLongArray(value));
             return this;
         }
@@ -510,7 +510,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putFloat(String key, float value) {
+        public @NonNull Builder putFloat(@NonNull String key, float value) {
             mValues.put(key, value);
             return this;
         }
@@ -522,7 +522,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putFloatArray(String key, float[] value) {
+        public @NonNull Builder putFloatArray(String key, float[] value) {
             mValues.put(key, convertPrimitiveFloatArray(value));
             return this;
         }
@@ -534,7 +534,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putDouble(String key, double value) {
+        public @NonNull Builder putDouble(@NonNull String key, double value) {
             mValues.put(key, value);
             return this;
         }
@@ -546,7 +546,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putDoubleArray(String key, double[] value) {
+        public @NonNull Builder putDoubleArray(@NonNull String key, double[] value) {
             mValues.put(key, convertPrimitiveDoubleArray(value));
             return this;
         }
@@ -558,7 +558,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putString(String key, String value) {
+        public @NonNull Builder putString(@NonNull String key, String value) {
             mValues.put(key, value);
             return this;
         }
@@ -570,7 +570,7 @@
          * @param value The value for this argument
          * @return The {@link Builder}
          */
-        public Builder putStringArray(String key, String[] value) {
+        public @NonNull Builder putStringArray(@NonNull String key, String[] value) {
             mValues.put(key, value);
             return this;
         }
@@ -584,7 +584,7 @@
          * @param data {@link Data} containing key-value pairs to add
          * @return The {@link Builder}
          */
-        public Builder putAll(@NonNull Data data) {
+        public @NonNull Builder putAll(@NonNull Data data) {
             putAll(data.mValues);
             return this;
         }
@@ -597,7 +597,7 @@
          * @param values A {@link Map} of key-value pairs to add
          * @return The {@link Builder}
          */
-        public Builder putAll(Map<String, Object> values) {
+        public @NonNull Builder putAll(@NonNull Map<String, Object> values) {
             for (Map.Entry<String, Object> entry : values.entrySet()) {
                 String key = entry.getKey();
                 Object value = entry.getValue();
@@ -643,7 +643,7 @@
          * @return The {@link Data} object containing all key-value pairs specified by this
          *         {@link Builder}.
          */
-        public Data build() {
+        public @NonNull Data build() {
             return new Data(mValues);
         }
     }
diff --git a/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java b/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
index 57849be..22778e4 100644
--- a/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/OneTimeWorkRequest.java
@@ -83,7 +83,7 @@
          * @param timeUnit The units of time for {@code duration}
          * @return The current {@link Builder}
          */
-        public Builder setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
+        public @NonNull Builder setInitialDelay(long duration, @NonNull TimeUnit timeUnit) {
             mWorkSpec.initialDelay = timeUnit.toMillis(duration);
             return this;
         }
@@ -95,7 +95,7 @@
          * @return The current {@link Builder}
          */
         @RequiresApi(26)
-        public Builder setInitialDelay(Duration duration) {
+        public @NonNull Builder setInitialDelay(@NonNull Duration duration) {
             mWorkSpec.initialDelay = duration.toMillis();
             return this;
         }
@@ -110,14 +110,14 @@
          *                    {@link OneTimeWorkRequest}
          * @return The current {@link Builder}
          */
-        public Builder setInputMerger(@NonNull Class<? extends InputMerger> inputMerger) {
+        public @NonNull Builder setInputMerger(@NonNull Class<? extends InputMerger> inputMerger) {
             mWorkSpec.inputMergerClassName = inputMerger.getName();
             return this;
         }
 
 
         @Override
-        public OneTimeWorkRequest build() {
+        public @NonNull OneTimeWorkRequest build() {
             if (mBackoffCriteriaSet
                     && Build.VERSION.SDK_INT >= 23
                     && mWorkSpec.constraints.requiresDeviceIdle()) {
@@ -128,7 +128,7 @@
         }
 
         @Override
-        Builder getThis() {
+        @NonNull Builder getThis() {
             return this;
         }
     }
diff --git a/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java b/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
index fe1a387..33f5492 100644
--- a/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
@@ -151,7 +151,7 @@
         }
 
         @Override
-        public PeriodicWorkRequest build() {
+        public @NonNull PeriodicWorkRequest build() {
             if (mBackoffCriteriaSet
                     && Build.VERSION.SDK_INT >= 23
                     && mWorkSpec.constraints.requiresDeviceIdle()) {
@@ -162,7 +162,7 @@
         }
 
         @Override
-        Builder getThis() {
+        @NonNull Builder getThis() {
             return this;
         }
     }
diff --git a/work/workmanager/src/main/java/androidx/work/State.java b/work/workmanager/src/main/java/androidx/work/State.java
index a654456..3541fac 100644
--- a/work/workmanager/src/main/java/androidx/work/State.java
+++ b/work/workmanager/src/main/java/androidx/work/State.java
@@ -17,45 +17,45 @@
 package androidx.work;
 
 /**
- * The current status of a unit of work.
+ * The current state of a unit of work.
  */
 public enum State {
 
     /**
-     * The status for work that is enqueued (hasn't completed and isn't running)
+     * The state for work that is enqueued (hasn't completed and isn't running)
      */
     ENQUEUED,
 
     /**
-     * The status for work that is currently being executed
+     * The state for work that is currently being executed
      */
     RUNNING,
 
     /**
-     * The status for work that has completed successfully
+     * The state for work that has completed successfully
      */
     SUCCEEDED,
 
     /**
-     * The status for work that has completed in a failure state
+     * The state for work that has completed in a failure state
      */
     FAILED,
 
     /**
-     * The status for work that is currently blocked because its prerequisites haven't finished
+     * The state for work that is currently blocked because its prerequisites haven't finished
      * successfully
      */
     BLOCKED,
 
     /**
-     * The status for work that has been cancelled and will not execute
+     * The state for work that has been cancelled and will not execute
      */
     CANCELLED;
 
     /**
      * Returns {@code true} if this State is considered finished.
      *
-     * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and {@link #CANCELLED} States
+     * @return {@code true} for {@link #SUCCEEDED}, {@link #FAILED}, and {@link #CANCELLED} states
      */
     public boolean isFinished() {
         return (this == SUCCEEDED || this == FAILED || this == CANCELLED);
diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java
index 0b27021..3a38ee7 100644
--- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java
+++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java
@@ -16,6 +16,7 @@
 
 package androidx.work;
 
+import android.support.annotation.NonNull;
 import android.support.annotation.WorkerThread;
 
 import java.util.List;
@@ -41,5 +42,5 @@
      * @return A {@link  List} of {@link WorkStatus}es
      */
     @WorkerThread
-    List<WorkStatus> getStatusesSync();
+    @NonNull List<WorkStatus> getStatusesSync();
 }
diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
index 9f633d8..3bb6b4d 100644
--- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
@@ -17,6 +17,7 @@
 package androidx.work;
 
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 
 import java.util.List;
@@ -124,7 +125,8 @@
      * own repository that must be updated or deleted in case someone cancels their work without
      * their prior knowledge.
      *
-     * @return The timestamp in milliseconds when a method that cancelled all work was last invoked
+     * @return The timestamp in milliseconds when a method that cancelled all work was last invoked;
+     *         this timestamp may be {@code 0L} if this never occurred.
      */
     @WorkerThread
     long getLastCancelAllTimeMillisSync();
@@ -148,10 +150,11 @@
      * expected to be called from a background thread.
      *
      * @param id The id of the work
-     * @return A {@link WorkStatus} associated with {@code id}
+     * @return A {@link WorkStatus} associated with {@code id}, or {@code null} if {@code id} is not
+     *         known to WorkManager
      */
     @WorkerThread
-    WorkStatus getStatusByIdSync(@NonNull UUID id);
+    @Nullable WorkStatus getStatusByIdSync(@NonNull UUID id);
 
     /**
      * Gets the {@link WorkStatus} for all work with a given tag in a synchronous fashion.  This
@@ -161,7 +164,7 @@
      * @return A list of {@link WorkStatus} for work tagged with {@code tag}
      */
     @WorkerThread
-    List<WorkStatus> getStatusesByTagSync(@NonNull String tag);
+    @NonNull List<WorkStatus> getStatusesByTagSync(@NonNull String tag);
 
     /**
      * Gets the {@link WorkStatus} for all work for the chain of work with a given unique name in a
@@ -171,5 +174,5 @@
      * @return A list of {@link WorkStatus} for work in the chain named {@code uniqueWorkName}
      */
     @WorkerThread
-    List<WorkStatus> getStatusesForUniqueWorkSync(@NonNull String uniqueWorkName);
+    @NonNull List<WorkStatus> getStatusesForUniqueWorkSync(@NonNull String uniqueWorkName);
 }
diff --git a/work/workmanager/src/main/java/androidx/work/WorkContinuation.java b/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
index 80acc82..5c781b3 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkContinuation.java
@@ -36,7 +36,7 @@
      * @return A {@link WorkContinuation} that allows for further chaining of dependent
      *         {@link OneTimeWorkRequest}
      */
-    public final WorkContinuation then(@NonNull OneTimeWorkRequest... work) {
+    public final @NonNull WorkContinuation then(@NonNull OneTimeWorkRequest... work) {
         return then(Arrays.asList(work));
     }
 
@@ -48,7 +48,7 @@
      * @return A {@link WorkContinuation} that allows for further chaining of dependent
      *         {@link OneTimeWorkRequest}
      */
-    public abstract WorkContinuation then(@NonNull List<OneTimeWorkRequest> work);
+    public abstract @NonNull WorkContinuation then(@NonNull List<OneTimeWorkRequest> work);
 
     /**
      * Returns a {@link LiveData} list of {@link WorkStatus} that provides information about work,
@@ -57,7 +57,7 @@
      *
      * @return A {@link LiveData} containing a list of {@link WorkStatus}es
      */
-    public abstract LiveData<List<WorkStatus>> getStatuses();
+    public abstract @NonNull LiveData<List<WorkStatus>> getStatuses();
 
     /**
      * Enqueues the instance of {@link WorkContinuation} on the background thread.
@@ -70,7 +70,7 @@
      * @return A {@link SynchronousWorkContinuation} object, which gives access to synchronous
      *         methods
      */
-    public abstract SynchronousWorkContinuation synchronous();
+    public abstract @NonNull SynchronousWorkContinuation synchronous();
 
     /**
      * Combines multiple {@link WorkContinuation}s to allow for complex chaining.
@@ -79,7 +79,7 @@
      *                      return value
      * @return A {@link WorkContinuation} that allows further chaining
      */
-    public static WorkContinuation combine(@NonNull WorkContinuation... continuations) {
+    public static @NonNull WorkContinuation combine(@NonNull WorkContinuation... continuations) {
         return combine(Arrays.asList(continuations));
     }
 
@@ -90,7 +90,7 @@
      *                      return value
      * @return A {@link WorkContinuation} that allows further chaining
      */
-    public static WorkContinuation combine(@NonNull List<WorkContinuation> continuations) {
+    public static @NonNull WorkContinuation combine(@NonNull List<WorkContinuation> continuations) {
         if (continuations.size() < 2) {
             throw new IllegalArgumentException(
                     "WorkContinuation.combine() needs at least 2 continuations.");
@@ -109,7 +109,7 @@
      *                      {@link OneTimeWorkRequest} provided.
      * @return A {@link WorkContinuation} that allows further chaining
      */
-    public static WorkContinuation combine(
+    public static @NonNull WorkContinuation combine(
             @NonNull OneTimeWorkRequest work,
             @NonNull WorkContinuation... continuations) {
         return combine(work, Arrays.asList(continuations));
@@ -125,7 +125,7 @@
      *                      {@link OneTimeWorkRequest} provided.
      * @return A {@link WorkContinuation} that allows further chaining
      */
-    public static WorkContinuation combine(
+    public static @NonNull WorkContinuation combine(
             @NonNull OneTimeWorkRequest work,
             @NonNull List<WorkContinuation> continuations) {
         return continuations.get(0).combineInternal(work, continuations);
@@ -135,7 +135,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    protected abstract WorkContinuation combineInternal(
+    protected abstract @NonNull WorkContinuation combineInternal(
             @Nullable OneTimeWorkRequest work,
             @NonNull List<WorkContinuation> continuations);
 }
diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java
index 5960e50..939192a 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java
@@ -121,15 +121,35 @@
     /**
      * Retrieves the {@code default} singleton instance of {@link WorkManager}.
      *
-     * @return The singleton instance of {@link WorkManager}
+     * @return The singleton instance of {@link WorkManager}; this may be {@code null} in unusual
+     *         circumstances where you have disabled automatic initialization and have failed to
+     *         manually call {@link #initialize(Context, Configuration)}.
+     * @throws IllegalStateException If WorkManager is not initialized properly.  This is most
+     *         likely because you disabled the automatic initialization but forgot to manually
+     *         call {@link WorkManager#initialize(Context, Configuration)}.
      */
-    public static WorkManager getInstance() {
-        return WorkManagerImpl.getInstance();
+    public static @NonNull WorkManager getInstance() {
+        WorkManager workManager = WorkManagerImpl.getInstance();
+        if (workManager == null) {
+            throw new IllegalStateException("WorkManager is not initialized properly.  The most "
+                    + "likely cause is that you disabled WorkManagerInitializer in your manifest "
+                    + "but forgot to call WorkManager#initialize in your Application#onCreate or a "
+                    + "ContentProvider.");
+        } else {
+            return workManager;
+        }
     }
 
     /**
-     * Used to do a one-time initialization of the {@link WorkManager} singleton with the default
-     * configuration.
+     * Used to do a one-time initialization of the {@link WorkManager} singleton with a custom
+     * {@link Configuration}.  By default, this method should not be called because WorkManager is
+     * automatically initialized.  To initialize WorkManager yourself, please follow these steps:
+     * <p><ul>
+     * <li>Disable {@code androidx.work.impl.WorkManagerInitializer} in your manifest
+     * <li>In {@code Application#onCreate} or a {@code ContentProvider}, call this method before
+     * calling {@link WorkManager#getInstance()}
+     * </ul></p>
+     * This method has no effect if WorkManager is already initialized.
      *
      * @param context A {@link Context} object for configuration purposes. Internally, this class
      *                will call {@link Context#getApplicationContext()}, so you may safely pass in
@@ -164,7 +184,7 @@
      * @return A {@link WorkContinuation} that allows for further chaining of dependent
      *         {@link OneTimeWorkRequest}
      */
-    public final WorkContinuation beginWith(@NonNull OneTimeWorkRequest...work) {
+    public final @NonNull WorkContinuation beginWith(@NonNull OneTimeWorkRequest...work) {
         return beginWith(Arrays.asList(work));
     }
 
@@ -176,7 +196,7 @@
      * @return A {@link WorkContinuation} that allows for further chaining of dependent
      *         {@link OneTimeWorkRequest}
      */
-    public abstract WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work);
+    public abstract @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work);
 
     /**
      * This method allows you to begin unique chains of work for situations where you only want one
@@ -201,7 +221,7 @@
      *             as a child of all leaf nodes labelled with {@code uniqueWorkName}.
      * @return A {@link WorkContinuation} that allows further chaining
      */
-    public final WorkContinuation beginUniqueWork(
+    public final @NonNull WorkContinuation beginUniqueWork(
             @NonNull String uniqueWorkName,
             @NonNull ExistingWorkPolicy existingWorkPolicy,
             @NonNull OneTimeWorkRequest... work) {
@@ -231,7 +251,7 @@
      *             as a child of all leaf nodes labelled with {@code uniqueWorkName}.
      * @return A {@link WorkContinuation} that allows further chaining
      */
-    public abstract WorkContinuation beginUniqueWork(
+    public abstract @NonNull WorkContinuation beginUniqueWork(
             @NonNull String uniqueWorkName,
             @NonNull ExistingWorkPolicy existingWorkPolicy,
             @NonNull List<OneTimeWorkRequest> work);
@@ -307,17 +327,19 @@
      * must be updated or deleted in case someone cancels their work without their prior knowledge.
      *
      * @return A {@link LiveData} of the timestamp in milliseconds when method that cancelled all
-     *         work was last invoked
+     *         work was last invoked; this timestamp may be {@code 0L} if this never occurred.
      */
-    public abstract LiveData<Long> getLastCancelAllTimeMillis();
+    public abstract @NonNull LiveData<Long> getLastCancelAllTimeMillis();
 
     /**
      * Gets a {@link LiveData} of the {@link WorkStatus} for a given work id.
      *
      * @param id The id of the work
-     * @return A {@link LiveData} of the {@link WorkStatus} associated with {@code id}
+     * @return A {@link LiveData} of the {@link WorkStatus} associated with {@code id}; note that
+     *         this {@link WorkStatus} may be {@code null} if {@code id} is not known to
+     *         WorkManager.
      */
-    public abstract LiveData<WorkStatus> getStatusById(@NonNull UUID id);
+    public abstract @NonNull LiveData<WorkStatus> getStatusById(@NonNull UUID id);
 
     /**
      * Gets a {@link LiveData} of the {@link WorkStatus} for all work for a given tag.
@@ -325,7 +347,7 @@
      * @param tag The tag of the work
      * @return A {@link LiveData} list of {@link WorkStatus} for work tagged with {@code tag}
      */
-    public abstract LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag);
+    public abstract @NonNull LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag);
 
     /**
      * Gets a {@link LiveData} of the {@link WorkStatus} for all work in a work chain with a given
@@ -335,7 +357,7 @@
      * @return A {@link LiveData} of the {@link WorkStatus} for work in the chain named
      *         {@code uniqueWorkName}
      */
-    public abstract LiveData<List<WorkStatus>> getStatusesForUniqueWork(
+    public abstract @NonNull LiveData<List<WorkStatus>> getStatusesForUniqueWork(
             @NonNull String uniqueWorkName);
 
     /**
@@ -343,7 +365,7 @@
      *
      * @return A {@link SynchronousWorkManager} object, which gives access to synchronous methods
      */
-    public abstract SynchronousWorkManager synchronous();
+    public abstract @NonNull SynchronousWorkManager synchronous();
 
     /**
      * @hide
diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
index cfb72b8..7a9602c 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
@@ -68,7 +68,7 @@
      *
      * @return The identifier for this unit of work
      */
-    public UUID getId() {
+    public @NonNull UUID getId() {
         return mId;
     }
 
@@ -79,7 +79,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public String getStringId() {
+    public @NonNull String getStringId() {
         return mId.toString();
     }
 
@@ -90,7 +90,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public WorkSpec getWorkSpec() {
+    public @NonNull WorkSpec getWorkSpec() {
         return mWorkSpec;
     }
 
@@ -101,7 +101,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public Set<String> getTags() {
+    public @NonNull Set<String> getTags() {
         return mTags;
     }
 
@@ -136,7 +136,7 @@
          * @param timeUnit The {@link TimeUnit} for {@code backoffDelay}
          * @return The current {@link Builder}
          */
-        public B setBackoffCriteria(
+        public @NonNull B setBackoffCriteria(
                 @NonNull BackoffPolicy backoffPolicy,
                 long backoffDelay,
                 @NonNull TimeUnit timeUnit) {
@@ -152,7 +152,7 @@
          * @param constraints The constraints for the work
          * @return The current {@link Builder}
          */
-        public B setConstraints(@NonNull Constraints constraints) {
+        public @NonNull B setConstraints(@NonNull Constraints constraints) {
             mWorkSpec.constraints = constraints;
             return getThis();
         }
@@ -163,7 +163,7 @@
          * @param inputData key/value pairs that will be provided to the {@link Worker} class
          * @return The current {@link Builder}
          */
-        public B setInputData(@NonNull Data inputData) {
+        public @NonNull B setInputData(@NonNull Data inputData) {
             mWorkSpec.input = inputData;
             return getThis();
         }
@@ -175,7 +175,7 @@
          * @param tag A tag for identifying the work in queries.
          * @return The current {@link Builder}
          */
-        public B addTag(@NonNull String tag) {
+        public @NonNull B addTag(@NonNull String tag) {
             mTags.add(tag);
             return getThis();
         }
@@ -196,7 +196,7 @@
          * @param timeUnit The unit of time for {@code duration}
          * @return The current {@link Builder}
          */
-        public B keepResultsForAtLeast(long duration, @NonNull TimeUnit timeUnit) {
+        public @NonNull B keepResultsForAtLeast(long duration, @NonNull TimeUnit timeUnit) {
             mWorkSpec.minimumRetentionDuration = timeUnit.toMillis(duration);
             return getThis();
         }
@@ -216,7 +216,7 @@
          * @return The current {@link Builder}
          */
         @RequiresApi(26)
-        public B keepResultsForAtLeast(@NonNull Duration duration) {
+        public @NonNull B keepResultsForAtLeast(@NonNull Duration duration) {
             mWorkSpec.minimumRetentionDuration = duration.toMillis();
             return getThis();
         }
@@ -226,9 +226,9 @@
          *
          * @return The concrete implementation of the work associated with this builder
          */
-        public abstract W build();
+        public abstract @NonNull W build();
 
-        abstract B getThis();
+        abstract @NonNull B getThis();
 
         /**
          * Set the initial state for this work.  Used in testing only.
@@ -239,7 +239,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @VisibleForTesting
-        public B setInitialState(@NonNull State state) {
+        public @NonNull B setInitialState(@NonNull State state) {
             mWorkSpec.state = state;
             return getThis();
         }
@@ -253,7 +253,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @VisibleForTesting
-        public B setInitialRunAttemptCount(int runAttemptCount) {
+        public @NonNull B setInitialRunAttemptCount(int runAttemptCount) {
             mWorkSpec.runAttemptCount = runAttemptCount;
             return getThis();
         }
@@ -268,7 +268,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @VisibleForTesting
-        public B setPeriodStartTime(long periodStartTime, @NonNull TimeUnit timeUnit) {
+        public @NonNull B setPeriodStartTime(long periodStartTime, @NonNull TimeUnit timeUnit) {
             mWorkSpec.periodStartTime = timeUnit.toMillis(periodStartTime);
             return getThis();
         }
@@ -283,7 +283,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         @VisibleForTesting
-        public B setScheduleRequestedAt(
+        public @NonNull B setScheduleRequestedAt(
                 long scheduleRequestedAt,
                 @NonNull TimeUnit timeUnit) {
             mWorkSpec.scheduleRequestedAt = timeUnit.toMillis(scheduleRequestedAt);
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..041f788 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -65,41 +65,38 @@
             @NonNull Configuration configuration,
             @NonNull WorkDatabase workDatabase,
             List<Scheduler> schedulers) {
-
-        WorkSpecDao workSpecDao = workDatabase.workSpecDao();
-        List<WorkSpec> eligibleWorkSpecs =
-                workSpecDao.getEligibleWorkForScheduling(
-                        configuration.getMaxSchedulerLimit());
-        scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs);
-    }
-
-    private static void scheduleInternal(
-            @NonNull WorkDatabase workDatabase,
-            List<Scheduler> schedulers,
-            List<WorkSpec> workSpecs) {
-
-        if (workSpecs == null || schedulers == null) {
+        if (schedulers == null || schedulers.size() == 0) {
             return;
         }
 
-        long now = System.currentTimeMillis();
         WorkSpecDao workSpecDao = workDatabase.workSpecDao();
-        // Mark all the WorkSpecs as scheduled.
-        // Calls to Scheduler#schedule() could potentially result in more schedules
-        // on a separate thread. Therefore, this needs to be done first.
+        List<WorkSpec> eligibleWorkSpecs;
+
         workDatabase.beginTransaction();
         try {
-            for (WorkSpec workSpec : workSpecs) {
-                workSpecDao.markWorkSpecScheduled(workSpec.id, now);
+            eligibleWorkSpecs = workSpecDao.getEligibleWorkForScheduling(
+                    configuration.getMaxSchedulerLimit());
+            if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
+                long now = System.currentTimeMillis();
+
+                // Mark all the WorkSpecs as scheduled.
+                // Calls to Scheduler#schedule() could potentially result in more schedules
+                // on a separate thread. Therefore, this needs to be done first.
+                for (WorkSpec workSpec : eligibleWorkSpecs) {
+                    workSpecDao.markWorkSpecScheduled(workSpec.id, now);
+                }
             }
             workDatabase.setTransactionSuccessful();
         } finally {
             workDatabase.endTransaction();
         }
-        WorkSpec[] eligibleWorkSpecsArray = workSpecs.toArray(new WorkSpec[0]);
-        // Delegate to the underlying scheduler.
-        for (Scheduler scheduler : schedulers) {
-            scheduler.schedule(eligibleWorkSpecsArray);
+
+        if (eligibleWorkSpecs != null && eligibleWorkSpecs.size() > 0) {
+            WorkSpec[] eligibleWorkSpecsArray = eligibleWorkSpecs.toArray(new WorkSpec[0]);
+            // Delegate to the underlying scheduler.
+            for (Scheduler scheduler : schedulers) {
+                scheduler.schedule(eligibleWorkSpecsArray);
+            }
         }
     }
 
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
index 3713dca..6fdc186 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
@@ -148,7 +148,7 @@
     }
 
     @Override
-    public WorkContinuation then(List<OneTimeWorkRequest> work) {
+    public @NonNull WorkContinuation then(List<OneTimeWorkRequest> work) {
         // TODO (rahulrav@) We need to decide if we want to allow chaining of continuations after
         // an initial call to enqueue()
         return new WorkContinuationImpl(mWorkManagerImpl,
@@ -159,12 +159,12 @@
     }
 
     @Override
-    public LiveData<List<WorkStatus>> getStatuses() {
+    public @NonNull LiveData<List<WorkStatus>> getStatuses() {
         return mWorkManagerImpl.getStatusesById(mAllIds);
     }
 
     @Override
-    public List<WorkStatus> getStatusesSync() {
+    public @NonNull List<WorkStatus> getStatusesSync() {
         if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
             throw new IllegalStateException("Cannot getStatusesSync on main thread!");
         }
@@ -201,12 +201,12 @@
     }
 
     @Override
-    public SynchronousWorkContinuation synchronous() {
+    public @NonNull SynchronousWorkContinuation synchronous() {
         return this;
     }
 
     @Override
-    protected WorkContinuation combineInternal(
+    protected @NonNull WorkContinuation combineInternal(
             @Nullable OneTimeWorkRequest work,
             @NonNull List<WorkContinuation> continuations) {
 
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 e5d5f9d..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,12 +59,14 @@
         WorkTag.class,
         SystemIdInfo.class,
         WorkName.class},
-        version = 2)
+        version = 4)
 @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...
@@ -82,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
@@ -95,9 +100,13 @@
         } else {
             builder = Room.databaseBuilder(context, WorkDatabase.class, DB_NAME);
         }
+
         return builder.addCallback(generateCleanupCallback())
                 .addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
-                .addMigrations(WorkDatabaseMigrations.MIGRATION_2_1)
+                .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 82a26ac..d75b514 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,15 @@
 
 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;
+
 /**
  * Migration helpers for {@link androidx.work.impl.WorkDatabase}.
  *
@@ -34,30 +40,27 @@
     }
 
     // Known WorkDatabase versions
-    private static final int VERSION_1 = 1;
-    private static final int VERSION_2 = 2;
+    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`"
                     + " INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`), FOREIGN KEY(`work_spec_id`)"
                     + " REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE CASCADE )";
 
-    private static final String CREATE_ALARM_INFO =
-            "CREATE TABLE IF NOT EXISTS `alarmInfo` (`work_spec_id` TEXT NOT NULL, `alarm_id`"
-                    + " INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`), FOREIGN KEY"
-                    + "(`work_spec_id`) REFERENCES `WorkSpec`(`id`) ON UPDATE CASCADE ON DELETE "
-                    + "CASCADE )";
-
     private static final String MIGRATE_ALARM_INFO_TO_SYSTEM_ID_INFO =
             "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";
+    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";
-    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
@@ -70,22 +73,39 @@
             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) "
+            database.execSQL("INSERT OR IGNORE 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.
+     * A {@link WorkDatabase} migration that reschedules all eligible Workers.
      */
-    public static Migration MIGRATION_2_1 = new Migration(VERSION_2, VERSION_1) {
+    public static class WorkMigration extends Migration {
+        final Context mContext;
+
+        public WorkMigration(@NonNull Context context, int startVersion, int endVersion) {
+            super(startVersion, endVersion);
+            mContext = context;
+        }
+
         @Override
         public void migrate(@NonNull SupportSQLiteDatabase database) {
-            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.
+            Preferences preferences = new Preferences(mContext);
+            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/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 0dcdda3..763a454 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -18,7 +18,9 @@
 
 import android.arch.core.util.Function;
 import android.arch.lifecycle.LiveData;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.os.Build;
 import android.os.Looper;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
@@ -37,6 +39,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;
@@ -72,6 +75,8 @@
     private List<Scheduler> mSchedulers;
     private Processor mProcessor;
     private Preferences mPreferences;
+    private boolean mForceStopRunnableCompleted;
+    private BroadcastReceiver.PendingResult mRescheduleReceiverResult;
 
     private static WorkManagerImpl sDelegatedInstance = null;
     private static WorkManagerImpl sDefaultInstance = null;
@@ -97,7 +102,7 @@
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public static WorkManagerImpl getInstance() {
+    public static @Nullable WorkManagerImpl getInstance() {
         synchronized (sLock) {
             if (sDelegatedInstance != null) {
                 return sDelegatedInstance;
@@ -172,6 +177,7 @@
                 getSchedulers(),
                 configuration.getExecutor());
         mPreferences = new Preferences(mContext);
+        mForceStopRunnableCompleted = false;
 
         // Checks for app force stops.
         mTaskExecutor.executeOnBackgroundThread(new ForceStopRunnable(context, this));
@@ -265,12 +271,12 @@
     }
 
     @Override
-    public WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work) {
+    public @NonNull WorkContinuation beginWith(@NonNull List<OneTimeWorkRequest> work) {
         return new WorkContinuationImpl(this, work);
     }
 
     @Override
-    public WorkContinuation beginUniqueWork(
+    public @NonNull WorkContinuation beginUniqueWork(
             @NonNull String uniqueWorkName,
             @NonNull ExistingWorkPolicy existingWorkPolicy,
             @NonNull List<OneTimeWorkRequest> work) {
@@ -370,7 +376,7 @@
     }
 
     @Override
-    public LiveData<Long> getLastCancelAllTimeMillis() {
+    public @NonNull LiveData<Long> getLastCancelAllTimeMillis() {
         return mPreferences.getLastCancelAllTimeMillisLiveData();
     }
 
@@ -392,7 +398,7 @@
     }
 
     @Override
-    public LiveData<WorkStatus> getStatusById(@NonNull UUID id) {
+    public @NonNull LiveData<WorkStatus> getStatusById(@NonNull UUID id) {
         WorkSpecDao dao = mWorkDatabase.workSpecDao();
         LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
                 dao.getWorkStatusPojoLiveDataForIds(Collections.singletonList(id.toString()));
@@ -423,7 +429,7 @@
     }
 
     @Override
-    public LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag) {
+    public @NonNull LiveData<List<WorkStatus>> getStatusesByTag(@NonNull String tag) {
         WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
         LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
                 workSpecDao.getWorkStatusPojoLiveDataForTag(tag);
@@ -431,7 +437,7 @@
     }
 
     @Override
-    public List<WorkStatus> getStatusesByTagSync(@NonNull String tag) {
+    public @NonNull List<WorkStatus> getStatusesByTagSync(@NonNull String tag) {
         assertBackgroundThread("Cannot call getStatusesByTagSync on main thread!");
         WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
         List<WorkSpec.WorkStatusPojo> input = workSpecDao.getWorkStatusPojoForTag(tag);
@@ -439,7 +445,8 @@
     }
 
     @Override
-    public LiveData<List<WorkStatus>> getStatusesForUniqueWork(@NonNull String uniqueWorkName) {
+    public @NonNull LiveData<List<WorkStatus>> getStatusesForUniqueWork(
+            @NonNull String uniqueWorkName) {
         WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
         LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
                 workSpecDao.getWorkStatusPojoLiveDataForName(uniqueWorkName);
@@ -447,7 +454,7 @@
     }
 
     @Override
-    public List<WorkStatus> getStatusesForUniqueWorkSync(@NonNull String uniqueWorkName) {
+    public @NonNull List<WorkStatus> getStatusesForUniqueWorkSync(@NonNull String uniqueWorkName) {
         assertBackgroundThread("Cannot call getStatusesByNameBlocking on main thread!");
         WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
         List<WorkSpec.WorkStatusPojo> input = workSpecDao.getWorkStatusPojoForName(uniqueWorkName);
@@ -455,7 +462,7 @@
     }
 
     @Override
-    public SynchronousWorkManager synchronous() {
+    public @NonNull SynchronousWorkManager synchronous() {
         return this;
     }
 
@@ -510,6 +517,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();
 
@@ -519,6 +531,42 @@
         Schedulers.schedule(getConfiguration(), getWorkDatabase(), getSchedulers());
     }
 
+    /**
+     * A way for {@link ForceStopRunnable} to tell {@link WorkManagerImpl} that it has completed.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void onForceStopRunnableCompleted() {
+        synchronized (sLock) {
+            mForceStopRunnableCompleted = true;
+            if (mRescheduleReceiverResult != null) {
+                mRescheduleReceiverResult.finish();
+                mRescheduleReceiverResult = null;
+            }
+        }
+    }
+
+    /**
+     * This method is invoked by
+     * {@link androidx.work.impl.background.systemalarm.RescheduleReceiver}
+     * after a call to {@link BroadcastReceiver#goAsync()}. Once {@link ForceStopRunnable} is done,
+     * we can safely call {@link BroadcastReceiver.PendingResult#finish()}.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public void setReschedulePendingResult(
+            @NonNull BroadcastReceiver.PendingResult rescheduleReceiverResult) {
+        synchronized (sLock) {
+            mRescheduleReceiverResult = rescheduleReceiverResult;
+            if (mForceStopRunnableCompleted) {
+                mRescheduleReceiverResult.finish();
+                mRescheduleReceiverResult = null;
+            }
+        }
+    }
+
     private void assertBackgroundThread(String errorMessage) {
         if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
             throw new IllegalStateException(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 0b8c44c..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,8 +21,10 @@
 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;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
@@ -94,18 +96,28 @@
             return;
         }
 
-        mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
-        if (mWorkSpec == null) {
-            Log.e(TAG,  String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
-            notifyListener(false, false);
-            return;
-        }
+        mWorkDatabase.beginTransaction();
+        try {
+            mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
+            if (mWorkSpec == null) {
+                Log.e(TAG, String.format("Didn't find WorkSpec for id %s", mWorkSpecId));
+                notifyListener(false, false);
+                return;
+            }
 
-        // Do a quick check to make sure we don't need to bail out in case this work is already
-        // running, finished, or is blocked.
-        if (mWorkSpec.state != ENQUEUED) {
-            notifyIncorrectStatus();
-            return;
+            // Do a quick check to make sure we don't need to bail out in case this work is already
+            // running, finished, or is blocked.
+            if (mWorkSpec.state != ENQUEUED) {
+                notifyIncorrectStatus();
+                mWorkDatabase.setTransactionSuccessful();
+                return;
+            }
+
+            // Needed for nested transactions, such as when we're in a dependent work request when
+            // using a SynchronousExecutor.
+            mWorkDatabase.setTransactionSuccessful();
+        } finally {
+            mWorkDatabase.endTransaction();
         }
 
         // Merge inputs.  This can be potentially expensive code, so this should not be done inside
@@ -157,6 +169,11 @@
                 result = mWorker.doWork();
             } catch (Exception | Error e) {
                 result = Worker.Result.FAILURE;
+                Log.e(TAG,
+                        String.format(
+                                "Worker %s failed because it threw an exception/error",
+                                mWorkSpecId),
+                        e);
             }
 
             try {
@@ -275,9 +292,9 @@
             if (currentState == ENQUEUED) {
                 mWorkSpecDao.setState(RUNNING, mWorkSpecId);
                 mWorkSpecDao.incrementWorkSpecRunAttemptCount(mWorkSpecId);
-                mWorkDatabase.setTransactionSuccessful();
                 setToRunning = true;
             }
+            mWorkDatabase.setTransactionSuccessful();
         } finally {
             mWorkDatabase.endTransaction();
         }
@@ -339,11 +356,29 @@
             mWorkSpecDao.setPeriodStartTime(mWorkSpecId, nextPeriodStartTime);
             mWorkSpecDao.setState(ENQUEUED, mWorkSpecId);
             mWorkSpecDao.resetWorkSpecRunAttemptCount(mWorkSpecId);
+            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();
             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/background/systemalarm/RescheduleReceiver.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java
index a331479..b8adec2 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/RescheduleReceiver.java
@@ -20,15 +20,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
 import android.util.Log;
 
-import androidx.work.WorkManager;
 import androidx.work.impl.WorkManagerImpl;
 
-import java.util.concurrent.TimeUnit;
-
 /**
  * Reschedules alarms on BOOT_COMPLETED and other similar scenarios.
  */
@@ -39,22 +34,15 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
-            if (WorkManager.getInstance() == null) {
+            WorkManagerImpl workManager = WorkManagerImpl.getInstance();
+            if (workManager == null) {
                 // WorkManager has not already been initialized.
                 Log.e(TAG,
                         "Cannot reschedule jobs. WorkManager needs to be initialized via a "
                                 + "ContentProvider#onCreate() or an Application#onCreate().");
             } else {
-                // This helps set up rescheduling of Jobs with JobScheduler. We are doing nothing
-                // for 10 seconds, to give ForceStopRunnable a chance to reschedule.
-                Handler handler = new Handler(Looper.getMainLooper());
                 final PendingResult pendingResult = goAsync();
-                handler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        pendingResult.finish();
-                    }
-                }, TimeUnit.SECONDS.toMillis(10));
+                workManager.setReschedulePendingResult(pendingResult);
             }
         } else {
             Intent reschedule = CommandHandler.createRescheduleIntent(context);
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/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index 87de105..c01e4fd 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,16 +64,16 @@
 
     @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();
         }
+        mWorkManager.onForceStopRunnableCompleted();
     }
 
     /**
@@ -98,12 +95,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,16 +124,7 @@
         return intent;
     }
 
-    /**
-     * Cancels all the persisted jobs in {@link JobScheduler}.
-     */
-    @VisibleForTesting
-    @TargetApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
-    public void cancelAllInJobScheduler() {
-        SystemJobScheduler.jobSchedulerCancelAll(mContext);
-    }
-
-    private void setAlarm(int alarmId) {
+    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.
         PendingIntent pendingIntent = getPendingIntent(alarmId, FLAG_UPDATE_CURRENT);
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 49cd262..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
@@ -20,7 +20,9 @@
 import android.arch.lifecycle.MutableLiveData;
 import android.content.Context;
 import android.content.SharedPreferences;
+import android.support.annotation.NonNull;
 import android.support.annotation.RestrictTo;
+import android.support.annotation.VisibleForTesting;
 
 /**
  * Preferences for WorkManager.
@@ -35,13 +37,17 @@
     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;
 
-    public Preferences(Context context) {
-        mSharedPreferences =
-                context.getSharedPreferences(PREFERENCES_FILE_NAME, Context.MODE_PRIVATE);
+    public Preferences(@NonNull Context context) {
+        this(context.getSharedPreferences(PREFERENCES_FILE_NAME, Context.MODE_PRIVATE));
+    }
+
+    @VisibleForTesting
+    public Preferences(@NonNull SharedPreferences preferences) {
+        mSharedPreferences = preferences;
     }
 
     /**
@@ -69,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, true).apply();
+    public void setNeedsReschedule(boolean needsReschedule) {
+        mSharedPreferences.edit().putBoolean(KEY_RESCHEDULE_NEEDED, needsReschedule).apply();
     }
 
     /**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java b/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
index 116e96c..e023fed 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/workers/ConstraintTrackingWorker.java
@@ -64,7 +64,7 @@
 
     @Override
     public @NonNull Result doWork() {
-        String className = getInputData().getString(ARGUMENT_CLASS_NAME, null);
+        String className = getInputData().getString(ARGUMENT_CLASS_NAME);
         if (TextUtils.isEmpty(className)) {
             Log.d(TAG, "No worker to delegate to.");
             return Result.FAILURE;
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
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
diff --git a/work/workmanager/src/test/java/androidx/work/DataTest.java b/work/workmanager/src/test/java/androidx/work/DataTest.java
index 3573b0a..04b502b 100644
--- a/work/workmanager/src/test/java/androidx/work/DataTest.java
+++ b/work/workmanager/src/test/java/androidx/work/DataTest.java
@@ -19,6 +19,7 @@
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import org.junit.Test;
@@ -125,14 +126,14 @@
         Data data = dataBuilder.build();
         assertThat(data.getInt("int", 0), is(1));
         assertThat(data.getFloat("float", 0f), is(99f));
-        assertThat(data.getString("String", null), is("two"));
+        assertThat(data.getString("String"), is("two"));
         long[] longArray = data.getLongArray("long array");
         assertThat(longArray, is(notNullValue()));
         assertThat(longArray.length, is(3));
         assertThat(longArray[0], is(1L));
         assertThat(longArray[1], is(2L));
         assertThat(longArray[2], is(3L));
-        assertThat(data.getString("null", "dummy"), is("dummy"));
+        assertThat(data.getString("null"), is(nullValue()));
     }
 
     @Test
diff --git a/work/workmanager/src/test/java/androidx/work/OverwritingInputMergerTest.java b/work/workmanager/src/test/java/androidx/work/OverwritingInputMergerTest.java
index 40e703c..b210a6d 100644
--- a/work/workmanager/src/test/java/androidx/work/OverwritingInputMergerTest.java
+++ b/work/workmanager/src/test/java/androidx/work/OverwritingInputMergerTest.java
@@ -42,7 +42,7 @@
         Data output = getOutputFor(input);
 
         assertThat(output.size(), is(1));
-        assertThat(output.getString(key, null), is(value));
+        assertThat(output.getString(key), is(value));
     }
 
     @Test
@@ -67,9 +67,9 @@
         Data output = getOutputFor(input1, input2);
 
         assertThat(output.size(), is(3));
-        assertThat(output.getString(key1, null), is(value1a));
-        assertThat(output.getString(key2, null), is(value2));
-        assertThat(output.getString(key3, null), is(value3));
+        assertThat(output.getString(key1), is(value1a));
+        assertThat(output.getString(key2), is(value2));
+        assertThat(output.getString(key3), is(value3));
     }
 
     private Data getOutputFor(Data... inputs) {