Merge "Remove unused methods from OnScrollListener." into pi-preview1-androidx-dev
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index b636814..12160e1 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -82,7 +82,7 @@
     /**
      * Version code for Navigation
      */
-    val NAVIGATION = Version("1.0.0-alpha01")
+    val NAVIGATION = Version("1.0.0-alpha02")
 
     /**
      * Version code for WorkManager
diff --git a/car/api/current.txt b/car/api/current.txt
index 25d25a1..c9daf51 100644
--- a/car/api/current.txt
+++ b/car/api/current.txt
@@ -296,6 +296,8 @@
     method public void onLayout(boolean, int, int, int, int);
     method public void onRestoreInstanceState(android.os.Parcelable);
     method public android.os.Parcelable onSaveInstanceState();
+    method public void pageDown();
+    method public void pageUp();
     method public int positionOf(android.view.View);
     method public void removeItemDecoration(androidx.recyclerview.widget.RecyclerView.ItemDecoration);
     method public void removeOnItemTouchListener(androidx.recyclerview.widget.RecyclerView.OnItemTouchListener);
diff --git a/car/src/main/java/androidx/car/widget/PagedListView.java b/car/src/main/java/androidx/car/widget/PagedListView.java
index a48bc33..29edaaf 100644
--- a/car/src/main/java/androidx/car/widget/PagedListView.java
+++ b/car/src/main/java/androidx/car/widget/PagedListView.java
@@ -16,8 +16,6 @@
 
 package androidx.car.widget;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.content.Context;
@@ -43,7 +41,6 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.RestrictTo;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
 import androidx.car.R;
@@ -818,11 +815,18 @@
     }
 
     /**
-     * Scrolls the contents of the RecyclerView up a page.
-     * @hide
+     * Scrolls the contents of the RecyclerView up a page. A page is defined as the height of the
+     * {@code PagedListView}.
+     *
+     * <p>The resulting first item in the list will be snapped to so that it is completely visible.
+     * If this is not possible due to the first item being taller than the containing
+     * {@code PagedListView}, then the snapping will not occur.
      */
-    @RestrictTo(LIBRARY_GROUP)
     public void pageUp() {
+        if (mRecyclerView.getLayoutManager() == null || mRecyclerView.getChildCount() == 0) {
+            return;
+        }
+
         // Use OrientationHelper to calculate scroll distance in order to match snapping behavior.
         OrientationHelper orientationHelper =
                 getOrientationHelper(mRecyclerView.getLayoutManager());
@@ -858,11 +862,18 @@
     }
 
     /**
-     * Scrolls the contents of the RecyclerView down a page.
-     * @hide
+     * Scrolls the contents of the RecyclerView down a page. A page is defined as the height of the
+     * {@code PagedListView}.
+     *
+     * <p>This method will attempt to bring the last item in the list as the first item. If the
+     * current first item in the list is taller than the {@code PagedListView}, then it will be
+     * scrolled the length of a page, but not snapped to.
      */
-    @RestrictTo(LIBRARY_GROUP)
     public void pageDown() {
+        if (mRecyclerView.getLayoutManager() == null || mRecyclerView.getChildCount() == 0) {
+            return;
+        }
+
         OrientationHelper orientationHelper =
                 getOrientationHelper(mRecyclerView.getLayoutManager());
         int screenSize = mRecyclerView.getHeight();
diff --git a/jetifier/jetifier/core/src/main/resources/default.config b/jetifier/jetifier/core/src/main/resources/default.config
index c7f70fb..2bbe6af 100644
--- a/jetifier/jetifier/core/src/main/resources/default.config
+++ b/jetifier/jetifier/core/src/main/resources/default.config
@@ -678,11 +678,21 @@
             "to": "androidx/constraintlayout/widget/{0}"
         },
 
-        # Uncomment once migrated
-        #{
-        #    "from": "android/support/v8/renderscript/(.*)",
-        #    "to": "androidx/renderscript/{0}"
-        #},
+        # Renderscript
+        {
+            # Not technically a class, but a string to replace in xml
+            "from": "com/android/support/v8/renderscript/EnableAsyncTeardown",
+            "to": "androidx/renderscript/EnableAsyncTeardown"
+        },
+        {
+            # Not technically a class, but a string to replace xml
+            "from": "com/android/support/v8/renderscript/EnableBlurWorkaround",
+            "to": "androidx/renderscript/EnableBlurWorkaround"
+        },
+        {
+            "from": "android/support/v8/renderscript/(.*)",
+            "to": "androidx/renderscript/{0}"
+        },
 
         #Binding
         {
@@ -828,96 +838,12 @@
             "to": "com/google/android/material/snackbar/SnackbarContentLayout{0}"
         },
         {
-            "from": "android/support/design/animation/(.*)",
-            "to": "com/google/android/material/animation/{0}"
-        },
-        {
-            "from": "android/support/design/bottomappbar/(.*)",
-            "to": "com/google/android/material/bottomappbar/{0}"
-        },
-        {
-            "from": "android/support/design/bottomnavigation/(.*)",
-            "to": "com/google/android/material/bottomnavigation/{0}"
-        },
-        {
-            "from": "android/support/design/button/(.*)",
-            "to": "com/google/android/material/button/{0}"
-        },
-        {
-            "from": "android/support/design/canvas/(.*)",
-            "to": "com/google/android/material/canvas/{0}"
-        },
-        {
-            "from": "android/support/design/card/(.*)",
-            "to": "com/google/android/material/card/{0}"
-        },
-        {
-            "from": "android/support/design/chip/(.*)",
-            "to": "com/google/android/material/chip/{0}"
-        },
-        {
-            "from": "android/support/design/circularreveal/(.*)",
-            "to": "com/google/android/material/circularreveal/{0}"
-        },
-        {
-            "from": "android/support/design/circularreveal/cardview/(.*)",
-            "to": "com/google/android/material/circularreveal/cardview/{0}"
-        },
-        {
-            "from": "android/support/design/circularreveal/coordinatorlayout/(.*)",
-            "to": "com/google/android/material/circularreveal/coordinatorlayout/{0}"
-        },
-        {
-            "from": "android/support/design/drawable/(.*)",
-            "to": "com/google/android/material/drawable/{0}"
-        },
-        {
-            "from": "android/support/design/expandable/(.*)",
-            "to": "com/google/android/material/expandable/{0}"
-        },
-        {
-            "from": "android/support/design/internal/(.*)",
-            "to": "com/google/android/material/internal/{0}"
-        },
-        {
-            "from": "android/support/design/math/(.*)",
-            "to": "com/google/android/material/math/{0}"
-        },
-        {
-            "from": "android/support/design/resources/(.*)",
-            "to": "com/google/android/material/resources/{0}"
-        },
-        {
-            "from": "android/support/design/ripple/(.*)",
-            "to": "com/google/android/material/ripple/{0}"
-        },
-        {
-            "from": "android/support/design/shape/(.*)",
-            "to": "com/google/android/material/shape/{0}"
-        },
-        {
-            "from": "android/support/design/snackbar/(.*)",
-            "to": "com/google/android/material/snackbar/{0}"
-        },
-        {
-            "from": "android/support/design/stateful/(.*)",
-            "to": "com/google/android/material/stateful/{0}"
-        },
-        {
-            "from": "android/support/design/theme/(.*)",
-            "to": "com/google/android/material/theme/{0}"
-        },
-        {
-            "from": "android/support/design/transformation/(.*)",
-            "to": "com/google/android/material/transformation/{0}"
-        },
-        {
             "from": "android/support/design/R(.*)",
             "to": "com/google/android/material/R{0}"
         },
         {
-            "from": "android/support/design/behavior/(.*)",
-            "to": "com/google/android/material/behavior/{0}"
+            "from": "android/support/design/(.*)",
+            "to": "com/google/android/material/{0}"
         },
 
         # Test
diff --git a/jetifier/jetifier/core/src/main/resources/default.generated.config b/jetifier/jetifier/core/src/main/resources/default.generated.config
index 71bac38..bcd622e 100644
--- a/jetifier/jetifier/core/src/main/resources/default.generated.config
+++ b/jetifier/jetifier/core/src/main/resources/default.generated.config
@@ -796,96 +796,12 @@
       "to": "com/google/android/material/snackbar/SnackbarContentLayout{0}"
     },
     {
-      "from": "android/support/design/animation/(.*)",
-      "to": "com/google/android/material/animation/{0}"
-    },
-    {
-      "from": "android/support/design/bottomappbar/(.*)",
-      "to": "com/google/android/material/bottomappbar/{0}"
-    },
-    {
-      "from": "android/support/design/bottomnavigation/(.*)",
-      "to": "com/google/android/material/bottomnavigation/{0}"
-    },
-    {
-      "from": "android/support/design/button/(.*)",
-      "to": "com/google/android/material/button/{0}"
-    },
-    {
-      "from": "android/support/design/canvas/(.*)",
-      "to": "com/google/android/material/canvas/{0}"
-    },
-    {
-      "from": "android/support/design/card/(.*)",
-      "to": "com/google/android/material/card/{0}"
-    },
-    {
-      "from": "android/support/design/chip/(.*)",
-      "to": "com/google/android/material/chip/{0}"
-    },
-    {
-      "from": "android/support/design/circularreveal/(.*)",
-      "to": "com/google/android/material/circularreveal/{0}"
-    },
-    {
-      "from": "android/support/design/circularreveal/cardview/(.*)",
-      "to": "com/google/android/material/circularreveal/cardview/{0}"
-    },
-    {
-      "from": "android/support/design/circularreveal/coordinatorlayout/(.*)",
-      "to": "com/google/android/material/circularreveal/coordinatorlayout/{0}"
-    },
-    {
-      "from": "android/support/design/drawable/(.*)",
-      "to": "com/google/android/material/drawable/{0}"
-    },
-    {
-      "from": "android/support/design/expandable/(.*)",
-      "to": "com/google/android/material/expandable/{0}"
-    },
-    {
-      "from": "android/support/design/internal/(.*)",
-      "to": "com/google/android/material/internal/{0}"
-    },
-    {
-      "from": "android/support/design/math/(.*)",
-      "to": "com/google/android/material/math/{0}"
-    },
-    {
-      "from": "android/support/design/resources/(.*)",
-      "to": "com/google/android/material/resources/{0}"
-    },
-    {
-      "from": "android/support/design/ripple/(.*)",
-      "to": "com/google/android/material/ripple/{0}"
-    },
-    {
-      "from": "android/support/design/shape/(.*)",
-      "to": "com/google/android/material/shape/{0}"
-    },
-    {
-      "from": "android/support/design/snackbar/(.*)",
-      "to": "com/google/android/material/snackbar/{0}"
-    },
-    {
-      "from": "android/support/design/stateful/(.*)",
-      "to": "com/google/android/material/stateful/{0}"
-    },
-    {
-      "from": "android/support/design/theme/(.*)",
-      "to": "com/google/android/material/theme/{0}"
-    },
-    {
-      "from": "android/support/design/transformation/(.*)",
-      "to": "com/google/android/material/transformation/{0}"
-    },
-    {
       "from": "android/support/design/R(.*)",
       "to": "com/google/android/material/R{0}"
     },
     {
-      "from": "android/support/design/behavior/(.*)",
-      "to": "com/google/android/material/behavior/{0}"
+      "from": "android/support/design/(.*)",
+      "to": "com/google/android/material/{0}"
     },
     {
       "from": "android/support/test/(.*)",
diff --git a/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomDocument.kt b/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomDocument.kt
index 31a376c..1bf7b24 100644
--- a/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomDocument.kt
+++ b/jetifier/jetifier/processor/src/main/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomDocument.kt
@@ -82,10 +82,7 @@
      * Changes are not saved back until requested.
      */
     fun applyRules(context: TransformationContext) {
-        if (context.rewritingSupportLib) {
-            rewriteOwnArtifactInfo(context)
-            hasChanged = true
-        }
+        tryRewriteOwnArtifactInfo(context)
 
         if (dependenciesGroup == null) {
             // Nothing to transform as this file has no dependencies section
@@ -117,7 +114,7 @@
         return PomDependency(groupIdNode.text, artifactIdNode.text, version.text)
     }
 
-    private fun rewriteOwnArtifactInfo(context: TransformationContext) {
+    private fun tryRewriteOwnArtifactInfo(context: TransformationContext) {
         val groupIdNode = document.rootElement
                 .getChild("groupId", document.rootElement.namespace)
         val artifactIdNode = document.rootElement
@@ -136,12 +133,13 @@
             groupIdNode.text = newDependency.groupId
             artifactIdNode.text = newDependency.artifactId
             version.text = newDependency.version
+            hasChanged = true
         }
     }
 
     private fun mapDependency(
-            dependency: PomDependency,
-            context: TransformationContext
+        dependency: PomDependency,
+        context: TransformationContext
     ): PomDependency {
         val rule = context.config.pomRewriteRules.firstOrNull { it.matches(dependency) }
         if (rule != null) {
diff --git a/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt b/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt
index 39d8ae1..aae077d 100644
--- a/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt
+++ b/jetifier/jetifier/processor/src/test/kotlin/com/android/tools/build/jetifier/processor/transform/pom/PomRewriteInZipTest.kt
@@ -84,7 +84,7 @@
         tempDir.delete()
     }
 
-    @Test fun rewritePomInZip_notRewritingSL_shouldNotRewrite() {
+    @Test fun rewritePomInZip_notRewritingSL_shouldStillRewrite() {
         val inputZipPath = "/pomRefactorTest/pomTest.zip"
 
         val processor = Processor.createProcessor(
@@ -107,13 +107,13 @@
 
         Truth.assertThat(returnedPom.fileName).isEqualTo("test.pom")
 
-        Truth.assertThat(content).contains("com.sample.my.group")
-        Truth.assertThat(content).contains("myArtifact")
-        Truth.assertThat(content).contains("1.0.0")
+        Truth.assertThat(content).doesNotContain("com.sample.my.group")
+        Truth.assertThat(content).doesNotContain("myArtifact")
+        Truth.assertThat(content).doesNotContain("1.0.0")
 
-        Truth.assertThat(content).doesNotContain("old.group")
-        Truth.assertThat(content).doesNotContain("myOldArtifact")
-        Truth.assertThat(content).doesNotContain("0.1.0")
+        Truth.assertThat(content).contains("old.group")
+        Truth.assertThat(content).contains("myOldArtifact")
+        Truth.assertThat(content).contains("0.1.0")
 
         tempDir.delete()
     }
diff --git a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java
index 7c06e62..1d18d29 100644
--- a/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java
+++ b/lifecycle/integration-tests/testapp/src/androidTest/java/androidx/lifecycle/SynchronousActivityLifecycleTest.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.os.Build;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.rule.UiThreadTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -60,6 +61,7 @@
                 activity -> getInstrumentation().callActivityOnCreate(activity, null));
     }
 
+    @SdkSuppress(maxSdkVersion = 27)
     @Test
     public void testOnStartCall() throws Throwable {
         testSynchronousCall(Lifecycle.Event.ON_START,
@@ -67,6 +69,7 @@
                 SynchronousActivityLifecycleTest::performStart);
     }
 
+    @SdkSuppress(maxSdkVersion = 27)
     @Test
     public void testOnResumeCall() throws Throwable {
         testSynchronousCall(Lifecycle.Event.ON_RESUME,
@@ -77,6 +80,7 @@
                 SynchronousActivityLifecycleTest::performResume);
     }
 
+    @SdkSuppress(maxSdkVersion = 27)
     @Test
     public void testOnStopCall() throws Throwable {
         testSynchronousCall(Lifecycle.Event.ON_STOP,
diff --git a/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
index 690ae90..1b88ef0 100644
--- a/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
+++ b/navigation/common/ktx/src/androidTest/java/androidx/navigation/NavOptionsBuilderTest.kt
@@ -37,6 +37,7 @@
                 navOptions.shouldLaunchSingleTop())
     }
 
+    @Suppress("DEPRECATION")
     @Test
     fun launchDocument() {
         val navOptions = navOptions {
diff --git a/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt b/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt
index fb011a2..12b738e 100644
--- a/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt
+++ b/navigation/common/ktx/src/main/java/androidx/navigation/NavOptionsBuilder.kt
@@ -51,6 +51,7 @@
      * This functions similarly to how [android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT]
      * works with activites.
      */
+    @Deprecated("Use the documentLaunchMode flag on the Activity")
     var launchDocument = false
 
     /**
@@ -99,6 +100,7 @@
 
     internal fun build() = builder.apply {
         setLaunchSingleTop(launchSingleTop)
+        @Suppress("DEPRECATION")
         setLaunchDocument(launchDocument)
         @Suppress("DEPRECATION")
         setClearTask(clearTask)
diff --git a/navigation/common/src/main/java/androidx/navigation/NavGraph.java b/navigation/common/src/main/java/androidx/navigation/NavGraph.java
index 4fc681d..0d03c38 100644
--- a/navigation/common/src/main/java/androidx/navigation/NavGraph.java
+++ b/navigation/common/src/main/java/androidx/navigation/NavGraph.java
@@ -27,12 +27,12 @@
 import android.support.v4.util.SparseArrayCompat;
 import android.util.AttributeSet;
 
+import androidx.navigation.common.R;
+
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
-import androidx.navigation.common.R;
-
 /**
  * NavGraph is a collection of {@link NavDestination} nodes fetchable by ID.
  *
@@ -187,6 +187,7 @@
                 : searchParents && getParent() != null ? getParent().findNode(resid) : null;
     }
 
+    @NonNull
     @Override
     public Iterator<NavDestination> iterator() {
         return new Iterator<NavDestination>() {
diff --git a/navigation/common/src/main/java/androidx/navigation/NavOptions.java b/navigation/common/src/main/java/androidx/navigation/NavOptions.java
index 4369b6c..ff815ec 100644
--- a/navigation/common/src/main/java/androidx/navigation/NavOptions.java
+++ b/navigation/common/src/main/java/androidx/navigation/NavOptions.java
@@ -131,7 +131,11 @@
      * <p>
      * This functions similarly to how {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}
      * works with activites.
+     * @deprecated As per the {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}
+     * documentation, it is recommended to use {@link android.R.attr#documentLaunchMode} on an
+     * Activity you wish to launch as a new document.
      */
+    @Deprecated
     public boolean shouldLaunchDocument() {
         return (mLaunchMode & LAUNCH_DOCUMENT) != 0;
     }
@@ -278,7 +282,11 @@
          * screen they will be taken to their home screen.</p>
          *
          * @param launchDocument true to launch a new document task
+         * @deprecated As per the {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT}
+         * documentation, it is recommended to use {@link android.R.attr#documentLaunchMode} on an
+         * Activity you wish to launch as a new document.
          */
+        @Deprecated
         @NonNull
         public Builder setLaunchDocument(boolean launchDocument) {
             if (launchDocument) {
@@ -337,6 +345,7 @@
          * @return this Builder
          * @see NavOptions#getEnterAnim()
          */
+        @NonNull
         public Builder setEnterAnim(@AnimRes @AnimatorRes int enterAnim) {
             mEnterAnim = enterAnim;
             return this;
diff --git a/navigation/common/src/main/java/androidx/navigation/Navigator.java b/navigation/common/src/main/java/androidx/navigation/Navigator.java
index f66acf5..a36b4cd 100644
--- a/navigation/common/src/main/java/androidx/navigation/Navigator.java
+++ b/navigation/common/src/main/java/androidx/navigation/Navigator.java
@@ -139,6 +139,28 @@
     public abstract boolean popBackStack();
 
     /**
+     * Called to ask for a {@link Bundle} representing the Navigator's state. This will be
+     * restored in {@link #onRestoreState(Bundle)}.
+     */
+    @Nullable
+    public Bundle onSaveState() {
+        return null;
+    }
+
+    /**
+     * Restore any state previously saved in {@link #onSaveState()}. This will be called before
+     * any calls to {@link #navigate(NavDestination, Bundle, NavOptions)} or
+     * {@link #popBackStack()}.
+     * <p>
+     * Calls to {@link #createDestination()} should not be dependent on any state restored here as
+     * {@link #createDestination()} can be called before the state is restored.
+     *
+     * @param savedState The state previously saved
+     */
+    public void onRestoreState(@NonNull Bundle savedState) {
+    }
+
+    /**
      * Add a listener to be notified when this navigator changes navigation destinations.
      *
      * <p>Most application code should use
diff --git a/navigation/common/src/main/java/androidx/navigation/SimpleNavigatorProvider.java b/navigation/common/src/main/java/androidx/navigation/SimpleNavigatorProvider.java
index bedf6c0..3df26c1 100644
--- a/navigation/common/src/main/java/androidx/navigation/SimpleNavigatorProvider.java
+++ b/navigation/common/src/main/java/androidx/navigation/SimpleNavigatorProvider.java
@@ -22,6 +22,7 @@
 import android.support.annotation.RestrictTo;
 
 import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Simple implementation of a {@link NavigatorProvider} that stores instances of
@@ -96,6 +97,10 @@
         return mNavigators.put(name, navigator);
     }
 
+    Map<String, Navigator<? extends NavDestination>> getNavigators() {
+        return mNavigators;
+    }
+
     private boolean validateName(String name) {
         return name != null && !name.isEmpty();
     }
diff --git a/navigation/common/src/test/java/androidx/navigation/EmptyNavigator.java b/navigation/common/src/test/java/androidx/navigation/EmptyNavigator.java
index 2b2174b..a445d39 100644
--- a/navigation/common/src/test/java/androidx/navigation/EmptyNavigator.java
+++ b/navigation/common/src/test/java/androidx/navigation/EmptyNavigator.java
@@ -17,6 +17,8 @@
 package androidx.navigation;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 /**
  * An empty {@link Navigator} used to test {@link SimpleNavigatorProvider}.
@@ -25,13 +27,15 @@
 class EmptyNavigator extends Navigator<NavDestination> {
     static final String NAME = "empty";
 
+    @NonNull
     @Override
     public NavDestination createDestination() {
         return new NavDestination(this);
     }
 
     @Override
-    public void navigate(NavDestination destination, Bundle args, NavOptions navOptions) {
+    public void navigate(@NonNull NavDestination destination, @Nullable Bundle args,
+            @Nullable NavOptions navOptions) {
         throw new IllegalStateException("navigate is not supported");
     }
 
diff --git a/navigation/common/src/test/java/androidx/navigation/NoNameNavigator.java b/navigation/common/src/test/java/androidx/navigation/NoNameNavigator.java
index 8b22459..7e5a35b 100644
--- a/navigation/common/src/test/java/androidx/navigation/NoNameNavigator.java
+++ b/navigation/common/src/test/java/androidx/navigation/NoNameNavigator.java
@@ -17,19 +17,23 @@
 package androidx.navigation;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 
 /**
  * A {@link Navigator} that does not have a {@link Navigator.Name} used to test
  * {@link SimpleNavigatorProvider}.
  */
 class NoNameNavigator extends Navigator<NavDestination> {
+    @NonNull
     @Override
     public NavDestination createDestination() {
         return new NavDestination(this);
     }
 
     @Override
-    public void navigate(NavDestination destination, Bundle args, NavOptions navOptions) {
+    public void navigate(@NonNull NavDestination destination, @Nullable Bundle args,
+            @Nullable NavOptions navOptions) {
         throw new IllegalStateException("navigate is not supported");
     }
 
diff --git a/navigation/fragment/src/androidTest/AndroidManifest.xml b/navigation/fragment/src/androidTest/AndroidManifest.xml
index 1ec370e..eabc827 100644
--- a/navigation/fragment/src/androidTest/AndroidManifest.xml
+++ b/navigation/fragment/src/androidTest/AndroidManifest.xml
@@ -22,5 +22,7 @@
         <activity android:name="androidx.navigation.fragment.test.XmlNavigationActivity" />
         <activity android:name="androidx.navigation.fragment.test.DynamicNavigationActivity" />
         <activity android:name="androidx.navigation.fragment.test.EmbeddedXmlActivity" />
+        <activity android:name="androidx.navigation.fragment.test.ImmediateNavigationActivity" />
+        <activity android:name="androidx.navigation.fragment.test.EmptyActivity" />
     </application>
 </manifest>
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.java b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.java
new file mode 100644
index 0000000..ec77800
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/FragmentNavigatorTest.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.navigation.fragment;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.lifecycle.Lifecycle;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+
+import androidx.navigation.NavOptions;
+import androidx.navigation.fragment.test.EmptyActivity;
+import androidx.navigation.fragment.test.EmptyFragment;
+import androidx.navigation.fragment.test.R;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SmallTest
+public class FragmentNavigatorTest {
+
+    @Rule
+    public ActivityTestRule<EmptyActivity> mActivityRule =
+            new ActivityTestRule<>(EmptyActivity.class);
+
+    private EmptyActivity mEmptyActivity;
+    private FragmentManager mFragmentManager;
+
+    @Before
+    public void setup() {
+        mEmptyActivity = mActivityRule.getActivity();
+        mFragmentManager = mEmptyActivity.getSupportFragmentManager();
+    }
+
+    @UiThreadTest
+    @Test
+    public void testNavigate() {
+        FragmentNavigator fragmentNavigator = new FragmentNavigator(mEmptyActivity,
+                mFragmentManager, R.id.container);
+        FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
+        destination.setFragmentClass(EmptyFragment.class);
+
+        fragmentNavigator.navigate(destination, null, null);
+        mFragmentManager.executePendingTransactions();
+        Fragment fragment = mFragmentManager.findFragmentById(R.id.container);
+        assertThat("Fragment should be added", fragment, is(notNullValue()));
+        assertThat("Fragment should be the correct type", fragment,
+                is(CoreMatchers.<Fragment>instanceOf(EmptyFragment.class)));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleTopInitial() {
+        FragmentNavigator fragmentNavigator = new FragmentNavigator(mEmptyActivity,
+                mFragmentManager, R.id.container);
+        FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
+        destination.setFragmentClass(EmptyFragment.class);
+
+        fragmentNavigator.navigate(destination, null, null);
+        mFragmentManager.executePendingTransactions();
+        Fragment fragment = mFragmentManager.findFragmentById(R.id.container);
+        assertThat("Fragment should be added", fragment, is(notNullValue()));
+
+        fragmentNavigator.navigate(destination, null,
+                new NavOptions.Builder().setLaunchSingleTop(true).build());
+        mFragmentManager.executePendingTransactions();
+        Fragment replacementFragment = mFragmentManager.findFragmentById(R.id.container);
+        assertThat("Replacement Fragment should be added", replacementFragment,
+                is(notNullValue()));
+        assertThat("Replacement Fragment should be the correct type", replacementFragment,
+                is(CoreMatchers.<Fragment>instanceOf(EmptyFragment.class)));
+        assertThat("Replacement should be a new instance", replacementFragment,
+                is(not(equalTo(fragment))));
+        assertThat("Old instance should be destroyed", fragment.getLifecycle().getCurrentState(),
+                is(equalTo(Lifecycle.State.DESTROYED)));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSingleTop() {
+        FragmentNavigator fragmentNavigator = new FragmentNavigator(mEmptyActivity,
+                mFragmentManager, R.id.container);
+        FragmentNavigator.Destination destination = fragmentNavigator.createDestination();
+        destination.setFragmentClass(EmptyFragment.class);
+
+        // First push an initial Fragment
+        fragmentNavigator.navigate(destination, null, null);
+
+        // Now push the Fragment that we want to replace with a singleTop operation
+        fragmentNavigator.navigate(destination, null, null);
+        mFragmentManager.executePendingTransactions();
+        Fragment fragment = mFragmentManager.findFragmentById(R.id.container);
+        assertThat("Fragment should be added", fragment, is(notNullValue()));
+
+        fragmentNavigator.navigate(destination, null,
+                new NavOptions.Builder().setLaunchSingleTop(true).build());
+        mFragmentManager.executePendingTransactions();
+        Fragment replacementFragment = mFragmentManager.findFragmentById(R.id.container);
+        assertThat("Replacement Fragment should be added", replacementFragment,
+                is(notNullValue()));
+        assertThat("Replacement Fragment should be the correct type", replacementFragment,
+                is(CoreMatchers.<Fragment>instanceOf(EmptyFragment.class)));
+        assertThat("Replacement should be a new instance", replacementFragment,
+                is(not(equalTo(fragment))));
+        assertThat("Old instance should be destroyed", fragment.getLifecycle().getCurrentState(),
+                is(equalTo(Lifecycle.State.DESTROYED)));
+    }
+}
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/ImmediateNavigationTest.java b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/ImmediateNavigationTest.java
new file mode 100644
index 0000000..9fb2609
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/ImmediateNavigationTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.navigation.fragment;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+
+import androidx.navigation.NavController;
+import androidx.navigation.fragment.test.ImmediateNavigationActivity;
+import androidx.navigation.fragment.test.R;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+@SmallTest
+public class ImmediateNavigationTest {
+
+    @Rule
+    public ActivityTestRule<ImmediateNavigationActivity> mActivityRule =
+            new ActivityTestRule<>(ImmediateNavigationActivity.class, false, false);
+
+    @Test
+    public void testNavigateInOnResume() throws Throwable {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        Intent intent = new Intent(instrumentation.getContext(),
+                ImmediateNavigationActivity.class);
+
+        final ImmediateNavigationActivity activity = mActivityRule.launchActivity(intent);
+        instrumentation.waitForIdleSync();
+        NavController navController = activity.getNavController();
+        assertThat(navController.getCurrentDestination().getId(), is(R.id.deep_link_test));
+    }
+}
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyActivity.java
similarity index 60%
copy from navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
copy to navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyActivity.java
index 0a21549..579a3dd 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyActivity.java
@@ -14,8 +14,16 @@
  * limitations under the License.
  */
 
-package androidx.navigation.safe.args.generator
+package androidx.navigation.fragment.test;
 
-data class XmlContext(private val name: String, private val line: Int, private val column: Int) {
-    fun createError(errorMsg: String) = Error("Error at $name:$line:$column $errorMsg")
-}
\ No newline at end of file
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.FragmentActivity;
+
+public class EmptyActivity extends FragmentActivity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.empty_activity);
+    }
+}
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.java b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.java
new file mode 100644
index 0000000..790d52c
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/EmptyFragment.java
@@ -0,0 +1,35 @@
+/*
+ * 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.navigation.fragment.test;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+public class EmptyFragment extends Fragment {
+    @Nullable
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        return new FrameLayout(requireContext());
+    }
+}
diff --git a/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/ImmediateNavigationActivity.java b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/ImmediateNavigationActivity.java
new file mode 100644
index 0000000..7d62372
--- /dev/null
+++ b/navigation/fragment/src/androidTest/java/androidx/navigation/fragment/test/ImmediateNavigationActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.navigation.fragment.test;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.navigation.NavController;
+import androidx.navigation.fragment.NavHostFragment;
+
+/**
+ * Test Navigation Activity that adds the {@link NavHostFragment} in XML.
+ *
+ * <p>You must call {@link NavController#setGraph(int)}
+ * to set the appropriate graph for your test.</p>
+ */
+public class ImmediateNavigationActivity extends BaseNavigationActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.immediate_navigation_activity);
+    }
+
+    public static class NavigateOnResumeFragment extends Fragment {
+        @Nullable
+        @Override
+        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+                @Nullable Bundle savedInstanceState) {
+            return new FrameLayout(requireContext());
+        }
+
+        @Override
+        public void onResume() {
+            super.onResume();
+            NavHostFragment.findNavController(this).navigate(R.id.deep_link_test);
+        }
+    }
+
+}
diff --git a/navigation/fragment/src/androidTest/res/layout/empty_activity.xml b/navigation/fragment/src/androidTest/res/layout/empty_activity.xml
new file mode 100644
index 0000000..c586e95
--- /dev/null
+++ b/navigation/fragment/src/androidTest/res/layout/empty_activity.xml
@@ -0,0 +1,23 @@
+<?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="match_parent"
+    android:id="@+id/container"
+/>
\ No newline at end of file
diff --git a/navigation/fragment/src/androidTest/res/layout/immediate_navigation_activity.xml b/navigation/fragment/src/androidTest/res/layout/immediate_navigation_activity.xml
new file mode 100644
index 0000000..2d8ec18
--- /dev/null
+++ b/navigation/fragment/src/androidTest/res/layout/immediate_navigation_activity.xml
@@ -0,0 +1,27 @@
+<?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.
+  -->
+
+<fragment
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/nav_host"
+    android:name="androidx.navigation.fragment.NavHostFragment"
+    app:navGraph="@navigation/nav_immediate"
+    app:defaultNavHost="true"
+/>
\ No newline at end of file
diff --git a/navigation/fragment/src/androidTest/res/navigation/nav_fragment_deep_link.xml b/navigation/fragment/src/androidTest/res/navigation/nav_fragment_deep_link.xml
new file mode 100644
index 0000000..00d26fd
--- /dev/null
+++ b/navigation/fragment/src/androidTest/res/navigation/nav_fragment_deep_link.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            app:startDestination="@+id/start_test">
+
+    <fragment
+            android:id="@+id/start_test"
+            android:name="androidx.navigation.fragment.test.EmptyFragment"/>
+
+    <fragment android:id="@+id/deep_link_test">
+        <deepLink app:uri="www.example.com/{test}" />
+    </fragment>
+</navigation>
diff --git a/navigation/fragment/src/androidTest/res/navigation/nav_immediate.xml b/navigation/fragment/src/androidTest/res/navigation/nav_immediate.xml
new file mode 100644
index 0000000..40c5073
--- /dev/null
+++ b/navigation/fragment/src/androidTest/res/navigation/nav_immediate.xml
@@ -0,0 +1,28 @@
+<?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.
+  -->
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            app:startDestination="@+id/start_test">
+
+    <fragment
+            android:id="@+id/start_test"
+            android:name="androidx.navigation.fragment.test.ImmediateNavigationActivity$NavigateOnResumeFragment"/>
+
+    <fragment
+            android:id="@+id/deep_link_test"
+            android:name="androidx.navigation.fragment.test.EmptyFragment"/>
+</navigation>
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
index 5b3ae09..2ee8ca6 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/FragmentNavigator.java
@@ -23,7 +23,6 @@
 import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
@@ -35,6 +34,7 @@
 import androidx.navigation.Navigator;
 import androidx.navigation.NavigatorProvider;
 
+import java.util.ArrayDeque;
 import java.util.HashMap;
 
 /**
@@ -44,32 +44,28 @@
  */
 @Navigator.Name("fragment")
 public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> {
+    private static final String KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds";
+
     private Context mContext;
     private FragmentManager mFragmentManager;
     private int mContainerId;
-    private int mBackStackCount;
+    private ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
 
     private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
             new FragmentManager.OnBackStackChangedListener() {
                 @Override
                 public void onBackStackChanged() {
-                    int newCount = mFragmentManager.getBackStackEntryCount();
-                    int backStackEffect;
-                    if (newCount < mBackStackCount) {
-                        backStackEffect = BACK_STACK_DESTINATION_POPPED;
-                    } else if (newCount > mBackStackCount) {
-                        backStackEffect = BACK_STACK_DESTINATION_ADDED;
-                    } else {
-                        backStackEffect = BACK_STACK_UNCHANGED;
+                    // The initial Fragment won't be on the back stack, so the
+                    // real count of destinations is the back stack entry count + 1
+                    int newCount = mFragmentManager.getBackStackEntryCount() + 1;
+                    if (newCount < mBackStack.size()) {
+                        // Handle cases where the user hit the system back button
+                        while (mBackStack.size() > newCount) {
+                            mBackStack.removeLast();
+                        }
+                        int destId = mBackStack.isEmpty() ? 0 : mBackStack.peekLast();
+                        dispatchOnNavigatorNavigated(destId, BACK_STACK_DESTINATION_POPPED);
                     }
-                    mBackStackCount = newCount;
-
-                    int destId = 0;
-                    StateFragment state = getState();
-                    if (state != null) {
-                        destId = state.mCurrentDestId;
-                    }
-                    dispatchOnNavigatorNavigated(destId, backStackEffect);
                 }
             };
 
@@ -79,13 +75,19 @@
         mFragmentManager = manager;
         mContainerId = containerId;
 
-        mBackStackCount = mFragmentManager.getBackStackEntryCount();
         mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
     }
 
     @Override
     public boolean popBackStack() {
-        return mFragmentManager.popBackStackImmediate();
+        if (mFragmentManager.getBackStackEntryCount() == 0) {
+            return false;
+        }
+        mFragmentManager.popBackStack();
+        mBackStack.removeLast();
+        int destId = mBackStack.isEmpty() ? 0 : mBackStack.peekLast();
+        dispatchOnNavigatorNavigated(destId, BACK_STACK_DESTINATION_POPPED);
+        return true;
     }
 
     @NonNull
@@ -126,40 +128,64 @@
 
         ft.replace(mContainerId, frag);
 
-        final StateFragment oldState = getState();
-        if (oldState != null) {
-            ft.remove(oldState);
-        }
-
         final @IdRes int destId = destination.getId();
-        final StateFragment newState = new StateFragment();
-        newState.mCurrentDestId = destId;
-        ft.add(newState, StateFragment.FRAGMENT_TAG);
-
-        final boolean initialNavigation = mFragmentManager.getFragments().isEmpty();
+        final boolean initialNavigation = mBackStack.isEmpty();
         final boolean isClearTask = navOptions != null && navOptions.shouldClearTask();
         // TODO Build first class singleTop behavior for fragments
-        final boolean isSingleTopReplacement = navOptions != null && oldState != null
+        final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
                 && navOptions.shouldLaunchSingleTop()
-                && oldState.mCurrentDestId == destId;
+                && mBackStack.peekLast() == destId;
+
+        int backStackEffect;
         if (!initialNavigation && !isClearTask && !isSingleTopReplacement) {
             ft.addToBackStack(getBackStackName(destId));
+            backStackEffect = BACK_STACK_DESTINATION_ADDED;
+        } else if (isSingleTopReplacement) {
+            // Single Top means we only want one instance on the back stack
+            if (mBackStack.size() > 1) {
+                // If the Fragment to be replaced is on the FragmentManager's
+                // back stack, a simple replace() isn't enough so we
+                // remove it from the back stack and put our replacement
+                // on the back stack in its place
+                mFragmentManager.popBackStack();
+                ft.addToBackStack(getBackStackName(destId));
+            }
+            backStackEffect = BACK_STACK_UNCHANGED;
         } else {
-            ft.runOnCommit(new Runnable() {
-                @Override
-                public void run() {
-                    dispatchOnNavigatorNavigated(destId, isSingleTopReplacement
-                            ? BACK_STACK_UNCHANGED
-                            : BACK_STACK_DESTINATION_ADDED);
-                }
-            });
+            backStackEffect = BACK_STACK_DESTINATION_ADDED;
         }
+        ft.setReorderingAllowed(true);
         ft.commit();
-        mFragmentManager.executePendingTransactions();
+        // The commit succeeded, update our view of the world
+        if (initialNavigation || !isSingleTopReplacement) {
+            mBackStack.add(destId);
+        }
+        dispatchOnNavigatorNavigated(destId, backStackEffect);
     }
 
-    private StateFragment getState() {
-        return (StateFragment) mFragmentManager.findFragmentByTag(StateFragment.FRAGMENT_TAG);
+    @Override
+    @Nullable
+    public Bundle onSaveState() {
+        Bundle b = new Bundle();
+        int[] backStack = new int[mBackStack.size()];
+        int index = 0;
+        for (Integer id : mBackStack) {
+            backStack[index++] = id;
+        }
+        b.putIntArray(KEY_BACK_STACK_IDS, backStack);
+        return b;
+    }
+
+    @Override
+    public void onRestoreState(@Nullable Bundle savedState) {
+        if (savedState != null) {
+            int[] backStack = savedState.getIntArray(KEY_BACK_STACK_IDS);
+            if (backStack != null) {
+                for (int destId : backStack) {
+                    mBackStack.add(destId);
+                }
+            }
+        }
     }
 
     /**
@@ -206,6 +232,7 @@
         }
 
         @SuppressWarnings("unchecked")
+        @NonNull
         private Class<? extends Fragment> getFragmentClassByName(Context context, String name) {
             if (name != null && name.charAt(0) == '.') {
                 name = context.getPackageName() + name;
@@ -229,7 +256,8 @@
          *              destination
          * @return this {@link Destination}
          */
-        public Destination setFragmentClass(Class<? extends Fragment> clazz) {
+        @NonNull
+        public Destination setFragmentClass(@NonNull Class<? extends Fragment> clazz) {
             mFragmentClass = clazz;
             return this;
         }
@@ -249,6 +277,7 @@
          * with this destination
          */
         @SuppressWarnings("ClassNewInstance")
+        @NonNull
         public Fragment createFragment(@Nullable Bundle args) {
             Class<? extends Fragment> clazz = getFragmentClass();
             if (clazz == null) {
@@ -268,32 +297,4 @@
             return f;
         }
     }
-
-    /**
-     * An internal fragment used by FragmentNavigator to track additional navigation state.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY)
-    public static class StateFragment extends Fragment {
-        static final String FRAGMENT_TAG = "android-support-nav:FragmentNavigator.StateFragment";
-
-        private static final String KEY_CURRENT_DEST_ID = "currentDestId";
-
-        int mCurrentDestId;
-
-        @Override
-        public void onCreate(@Nullable Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (savedInstanceState != null) {
-                mCurrentDestId = savedInstanceState.getInt(KEY_CURRENT_DEST_ID);
-            }
-        }
-
-        @Override
-        public void onSaveInstanceState(Bundle outState) {
-            super.onSaveInstanceState(outState);
-            outState.putInt(KEY_CURRENT_DEST_ID, mCurrentDestId);
-        }
-    }
 }
diff --git a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
index cf03dc1..379cf22 100644
--- a/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
+++ b/navigation/fragment/src/main/java/androidx/navigation/fragment/NavHostFragment.java
@@ -100,7 +100,7 @@
             if (findFragment instanceof NavHostFragment) {
                 return ((NavHostFragment) findFragment).getNavController();
             }
-            Fragment primaryNavFragment = findFragment.getFragmentManager()
+            Fragment primaryNavFragment = findFragment.requireFragmentManager()
                     .getPrimaryNavigationFragment();
             if (primaryNavFragment instanceof NavHostFragment) {
                 return ((NavHostFragment) primaryNavFragment).getNavController();
@@ -185,14 +185,16 @@
         // but it can stay here until we can add the necessary attr resources to
         // the fragment lib.
         if (mDefaultNavHost) {
-            getFragmentManager().beginTransaction().setPrimaryNavigationFragment(this).commit();
+            requireFragmentManager().beginTransaction()
+                    .setPrimaryNavigationFragment(this)
+                    .commit();
         }
     }
 
     @Override
     public void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        final Context context = getContext();
+        final Context context = requireContext();
 
         mNavController = new NavController(context);
         mNavController.getNavigatorProvider().addNavigator(createFragmentNavigator());
@@ -202,7 +204,9 @@
             navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
             if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
                 mDefaultNavHost = true;
-                getFragmentManager().beginTransaction().setPrimaryNavigationFragment(this).commit();
+                requireFragmentManager().beginTransaction()
+                        .setPrimaryNavigationFragment(this)
+                        .commit();
             }
         }
 
@@ -230,7 +234,7 @@
      */
     @NonNull
     protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
-        return new FragmentNavigator(getContext(), getChildFragmentManager(), getId());
+        return new FragmentNavigator(requireContext(), getChildFragmentManager(), getId());
     }
 
     @Nullable
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java
index 985645c..a9a56a1 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/AndroidFragment.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.NotificationCompat;
@@ -40,13 +41,13 @@
 public class AndroidFragment extends Fragment {
     @Nullable
     @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                              @Nullable Bundle savedInstanceState) {
         return inflater.inflate(R.layout.android_fragment, container, false);
     }
 
     @Override
-    public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull final View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
         TextView tv = view.findViewById(R.id.text);
         tv.setText(getArguments().getString("myarg"));
@@ -63,13 +64,13 @@
                         .setArguments(args)
                         .createPendingIntent();
                 NotificationManager notificationManager = (NotificationManager)
-                        getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+                        requireContext().getSystemService(Context.NOTIFICATION_SERVICE);
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                     notificationManager.createNotificationChannel(new NotificationChannel(
                             "deeplink", "Deep Links", NotificationManager.IMPORTANCE_HIGH));
                 }
                 NotificationCompat.Builder builder = new NotificationCompat.Builder(
-                        getContext(), "deeplink")
+                        requireContext(), "deeplink")
                         .setContentTitle("Navigation")
                         .setContentText("Deep link to Android")
                         .setSmallIcon(R.drawable.ic_android)
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.java b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.java
index 2a4950e..8144a67 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.java
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/MainFragment.java
@@ -17,6 +17,7 @@
 package androidx.navigation.testapp;
 
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
 import android.view.LayoutInflater;
@@ -38,18 +39,18 @@
 
     @Nullable
     @Override
-    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                              @Nullable Bundle savedInstanceState) {
         return inflater.inflate(R.layout.main_fragment, container, false);
     }
 
     @Override
-    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
-        TextView tv = (TextView) view.findViewById(R.id.text);
+        TextView tv = view.findViewById(R.id.text);
         tv.setText(getArguments().getString("myarg"));
 
-        Button b = (Button) view.findViewById(R.id.next_button);
+        Button b = view.findViewById(R.id.next_button);
         b.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next));
     }
 }
diff --git a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java
index 3a78c30..d3dacc3 100644
--- a/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java
+++ b/navigation/integration-tests/testapp/src/main/java/androidx/navigation/testapp/NavigationActivity.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.widget.BottomNavigationView;
 import android.support.design.widget.NavigationView;
@@ -66,7 +67,8 @@
             }
             navController.addOnNavigatedListener(new NavController.OnNavigatedListener() {
                 @Override
-                public void onNavigated(NavController controller, NavDestination destination) {
+                public void onNavigated(@NonNull NavController controller,
+                        @NonNull NavDestination destination) {
                     String dest;
                     try {
                         dest = getResources().getResourceName(destination.getId());
diff --git a/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml b/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
index 24fcf4f..392fd4a 100644
--- a/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
+++ b/navigation/integration-tests/testapp/src/main/res/navigation/nav_main.xml
@@ -16,12 +16,15 @@
     xmlns:tools="http://schemas.android.com/tools"
     app:startDestination="@+id/launcher_home"
     tools:ignore="DuplicateIds">
-    <fragment android:id="@+id/launcher_home"
-              android:label="@string/home"
-              android:name=".MainFragment">
-        <argument android:name="myarg" android:defaultValue="Home" />
-        <action android:id="@+id/next" app:destination="@+id/first_screen"/>
-    </fragment>
+    <navigation android:id="@+id/launcher_home"
+                app:startDestination="@+id/main"
+                android:label="@string/home">
+        <fragment android:id="@+id/main"
+                  android:name=".MainFragment">
+            <argument android:name="myarg" android:defaultValue="Home" />
+            <action android:id="@+id/next" app:destination="@+id/first_screen"/>
+        </fragment>
+    </navigation>
     <fragment android:id="@+id/android"
               android:label="@string/android"
               android:name="androidx.navigation.testapp.AndroidFragment">
diff --git a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
index 53704eb..ad28423 100644
--- a/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
+++ b/navigation/runtime/ktx/src/androidTest/java/androidx/navigation/ActivityNavigatorDestinationBuilderTest.kt
@@ -20,8 +20,8 @@
 import android.support.test.InstrumentationRegistry
 import android.support.test.filters.SmallTest
 import android.support.test.runner.AndroidJUnit4
-import junit.framework.Assert.assertTrue
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -55,7 +55,7 @@
                 DESTINATION_ID in graph)
         assertEquals("Destination should have ComponentName set",
                 TestActivity::class.java.name,
-                (graph[DESTINATION_ID] as ActivityNavigator.Destination).component.className)
+                (graph[DESTINATION_ID] as ActivityNavigator.Destination).component?.className)
     }
 
     @Test
diff --git a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.java b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.java
index 7a0e91b..b09bd9d 100644
--- a/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.java
+++ b/navigation/runtime/src/androidTest/java/androidx/navigation/NavControllerTest.java
@@ -207,6 +207,30 @@
     }
 
     @Test
+    public void testNavigateFromNestedThenNavigatorInstigatedPop() {
+        NavController navController = createNavController();
+        navController.setGraph(R.navigation.nav_nested_start_destination);
+        TestNavigator navigator = navController.getNavigatorProvider()
+                .getNavigator(TestNavigator.class);
+        assertThat(navController.getCurrentDestination().getId(), is(R.id.nested_test));
+        assertThat(navigator.mBackStack.size(), is(1));
+
+        navController.navigate(R.id.second_test);
+        assertThat(navController.getCurrentDestination().getId(), is(R.id.second_test));
+        assertThat(navigator.mBackStack.size(), is(2));
+
+        // A Navigator can pop a destination off its own back stack
+        // then inform the NavController via dispatchOnNavigatorNavigated
+        navigator.mBackStack.removeLast();
+        NavDestination newDestination = navigator.mBackStack.peekLast().first;
+        assertThat(newDestination, is(notNullValue()));
+        navigator.dispatchOnNavigatorNavigated(newDestination.getId(),
+                Navigator.BACK_STACK_DESTINATION_POPPED);
+        assertThat(navController.getCurrentDestination().getId(), is(R.id.nested_test));
+        assertThat(navigator.mBackStack.size(), is(1));
+    }
+
+    @Test
     public void testNavigateThenNavigateUp() {
         NavController navController = createNavController();
         navController.setGraph(R.navigation.nav_simple);
diff --git a/navigation/runtime/src/androidTest/res/navigation/nav_nested_start_destination.xml b/navigation/runtime/src/androidTest/res/navigation/nav_nested_start_destination.xml
index 03d80cb..347f20f 100644
--- a/navigation/runtime/src/androidTest/res/navigation/nav_nested_start_destination.xml
+++ b/navigation/runtime/src/androidTest/res/navigation/nav_nested_start_destination.xml
@@ -21,4 +21,6 @@
 
         <test android:id="@+id/nested_test"/>
     </navigation>
+
+    <test android:id="@+id/second_test"/>
 </navigation>
\ No newline at end of file
diff --git a/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java b/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
index 4a1fbb7..89bd170 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/ActivityNavigator.java
@@ -214,6 +214,7 @@
          * @param intent Intent to associated with this destination.
          * @return this {@link Destination}
          */
+        @NonNull
         public Destination setIntent(Intent intent) {
             mIntent = intent;
             return this;
@@ -223,6 +224,7 @@
          * Gets the Intent associated with this destination.
          * @return
          */
+        @Nullable
         public Intent getIntent() {
             return mIntent;
         }
@@ -233,6 +235,7 @@
          * @param name The component name of the Activity to start.
          * @return this {@link Destination}
          */
+        @NonNull
         public Destination setComponentName(ComponentName name) {
             if (mIntent == null) {
                 mIntent = new Intent();
@@ -245,6 +248,7 @@
          * Get the explicit {@link ComponentName} associated with this destination, if any
          * @return
          */
+        @Nullable
         public ComponentName getComponent() {
             if (mIntent == null) {
                 return null;
@@ -257,6 +261,7 @@
          * @param action The action string to use.
          * @return this {@link Destination}
          */
+        @NonNull
         public Destination setAction(String action) {
             if (mIntent == null) {
                 mIntent = new Intent();
@@ -268,6 +273,7 @@
         /**
          * Get the action used to start the Activity, if any
          */
+        @Nullable
         public String getAction() {
             if (mIntent == null) {
                 return null;
@@ -286,6 +292,7 @@
          * @see #setDataPattern(String)
          * @return this {@link Destination}
          */
+        @NonNull
         public Destination setData(Uri data) {
             if (mIntent == null) {
                 mIntent = new Intent();
@@ -297,6 +304,7 @@
         /**
          * Get the data URI used to start the Activity, if any
          */
+        @Nullable
         public Uri getData() {
             if (mIntent == null) {
                 return null;
@@ -315,6 +323,7 @@
          * @see #setData
          * @return this {@link Destination}
          */
+        @NonNull
         public Destination setDataPattern(String dataPattern) {
             mDataPattern = dataPattern;
             return this;
@@ -323,6 +332,7 @@
         /**
          * Gets the dynamic data URI pattern, if any
          */
+        @Nullable
         public String getDataPattern() {
             return mDataPattern;
         }
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavController.java b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
index 59f52c5..fe36c51 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavController.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavController.java
@@ -32,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
@@ -48,6 +49,10 @@
  * from a remote server.)</p>
  */
 public class NavController {
+    private static final String KEY_NAVIGATOR_STATE =
+            "android-support-nav:controller:navigatorState";
+    private static final String KEY_NAVIGATOR_STATE_NAMES =
+            "android-support-nav:controller:navigatorState:names";
     private static final String KEY_GRAPH_ID = "android-support-nav:controller:graphId";
     private static final String KEY_BACK_STACK_IDS = "android-support-nav:controller:backStackIds";
     static final String KEY_DEEP_LINK_IDS = "android-support-nav:controller:deepLinkIds";
@@ -64,6 +69,7 @@
     private NavInflater mInflater;
     private NavGraph mGraph;
     private int mGraphId;
+    private Bundle mNavigatorStateToRestore;
     private int[] mBackStackToRestore;
 
     private final Deque<NavDestination> mBackStack = new ArrayDeque<>();
@@ -88,30 +94,30 @@
     private final Navigator.OnNavigatorNavigatedListener mOnNavigatedListener =
             new Navigator.OnNavigatorNavigatedListener() {
                 @Override
-                public void onNavigatorNavigated(Navigator navigator, @IdRes int destId,
+                public void onNavigatorNavigated(@NonNull Navigator navigator, @IdRes int destId,
                         @Navigator.BackStackEffect int backStackEffect) {
                     if (destId != 0) {
+                        // First remove popped destinations off the back stack
+                        if (backStackEffect == Navigator.BACK_STACK_DESTINATION_POPPED) {
+                            while (!mBackStack.isEmpty()
+                                    && mBackStack.peekLast().getId() != destId) {
+                                mBackStack.removeLast();
+                            }
+                        }
                         NavDestination newDest = findDestination(destId);
                         if (newDest == null) {
                             throw new IllegalArgumentException("Navigator " + navigator
                                     + " reported navigation to unknown destination id "
                                     + NavDestination.getDisplayName(mContext, destId));
                         }
-                        switch (backStackEffect) {
-                            case Navigator.BACK_STACK_DESTINATION_POPPED:
-                                while (!mBackStack.isEmpty()
-                                        && mBackStack.peekLast().getId() != destId) {
-                                    mBackStack.removeLast();
-                                }
-                                break;
-                            case Navigator.BACK_STACK_DESTINATION_ADDED:
-                                mBackStack.add(newDest);
-                                break;
-                            case Navigator.BACK_STACK_UNCHANGED:
-                                // Don't update the back stack and don't dispatchOnNavigated
-                                return;
+                        if (backStackEffect == Navigator.BACK_STACK_DESTINATION_ADDED) {
+                            // Add the new destination to the back stack
+                            mBackStack.add(newDest);
                         }
-                        dispatchOnNavigated(newDest);
+                        // Don't dispatchOnNavigated if nothing changed
+                        if (backStackEffect != Navigator.BACK_STACK_UNCHANGED) {
+                            dispatchOnNavigated(newDest);
+                        }
                     }
                 }
             };
@@ -319,7 +325,7 @@
         }
     }
 
-    void dispatchOnNavigated(NavDestination destination) {
+    private void dispatchOnNavigated(NavDestination destination) {
         for (OnNavigatedListener listener : mOnNavigatedListeners) {
             listener.onNavigated(this, destination);
         }
@@ -335,14 +341,18 @@
      *     <meta-data android:name="android.nav.graph" android:resource="@xml/my_nav_graph" />
      * </pre>
      *
-     * <p>The inflated graph can be retrieved via {@link #getGraph()}.</p>
+     * <p>The inflated graph can be retrieved via {@link #getGraph()}. Calling this will have no
+     * effect if there is no metadata graph specified.</p>
      *
      * @see NavInflater#METADATA_KEY_GRAPH
      * @see NavInflater#inflateMetadataGraph()
      * @see #getGraph
      */
     public void setMetadataGraph() {
-        setGraph(getNavInflater().inflateMetadataGraph());
+        NavGraph metadataGraph = getNavInflater().inflateMetadataGraph();
+        if (metadataGraph != null) {
+            setGraph(metadataGraph);
+        }
     }
 
     /**
@@ -393,6 +403,19 @@
     }
 
     private void onGraphCreated() {
+        if (mNavigatorStateToRestore != null) {
+            ArrayList<String> navigatorNames = mNavigatorStateToRestore.getStringArrayList(
+                    KEY_NAVIGATOR_STATE_NAMES);
+            if (navigatorNames != null) {
+                for (String name : navigatorNames) {
+                    Navigator navigator = mNavigatorProvider.getNavigator(name);
+                    Bundle bundle = mNavigatorStateToRestore.getBundle(name);
+                    if (bundle != null) {
+                        navigator.onRestoreState(bundle);
+                    }
+                }
+            }
+        }
         if (mBackStackToRestore != null) {
             for (int destinationId : mBackStackToRestore) {
                 NavDestination node = findDestination(destinationId);
@@ -665,6 +688,24 @@
             b = new Bundle();
             b.putInt(KEY_GRAPH_ID, mGraphId);
         }
+        ArrayList<String> navigatorNames = new ArrayList<>();
+        Bundle navigatorState = new Bundle();
+        for (Map.Entry<String, Navigator<? extends NavDestination>> entry :
+                mNavigatorProvider.getNavigators().entrySet()) {
+            String name = entry.getKey();
+            Bundle savedState = entry.getValue().onSaveState();
+            if (savedState != null) {
+                navigatorNames.add(name);
+                navigatorState.putBundle(name, entry.getValue().onSaveState());
+            }
+        }
+        if (!navigatorNames.isEmpty()) {
+            if (b == null) {
+                b = new Bundle();
+            }
+            navigatorState.putStringArrayList(KEY_NAVIGATOR_STATE_NAMES, navigatorNames);
+            b.putBundle(KEY_NAVIGATOR_STATE, navigatorState);
+        }
         if (!mBackStack.isEmpty()) {
             if (b == null) {
                 b = new Bundle();
@@ -693,10 +734,11 @@
         }
 
         mGraphId = navState.getInt(KEY_GRAPH_ID);
+        mNavigatorStateToRestore = navState.getBundle(KEY_NAVIGATOR_STATE);
         mBackStackToRestore = navState.getIntArray(KEY_BACK_STACK_IDS);
         if (mGraphId != 0) {
-            // Set the graph right away, onGraphCreated will re-add the back stack
-            // from mBackStackToRestore
+            // Set the graph right away, onGraphCreated will handle restoring the
+            // rest of the saved state
             setGraph(mGraphId);
         }
     }
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.java b/navigation/runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.java
index 08ce72f..f09131e 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavDeepLinkBuilder.java
@@ -256,13 +256,15 @@
          * A Navigator that only parses the {@link NavDestination} attributes.
          */
         private final Navigator<NavDestination> mDestNavigator = new Navigator<NavDestination>() {
+            @NonNull
             @Override
             public NavDestination createDestination() {
                 return new NavDestination(this);
             }
 
             @Override
-            public void navigate(NavDestination destination, Bundle args, NavOptions navOptions) {
+            public void navigate(@NonNull NavDestination destination, @Nullable Bundle args,
+                    @Nullable NavOptions navOptions) {
                 throw new IllegalStateException("navigate is not supported");
             }
 
diff --git a/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java b/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java
index a96e4ad..4a1789a 100644
--- a/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java
+++ b/navigation/runtime/src/main/java/androidx/navigation/NavInflater.java
@@ -54,6 +54,7 @@
      * Navigation host implementations should do this automatically
      * if no navigation resource is otherwise supplied during host configuration.</p>
      */
+    @SuppressWarnings("WeakerAccess")
     public static final String METADATA_KEY_GRAPH = "android.nav.graph";
 
     private static final String TAG_ARGUMENT = "argument";
@@ -67,8 +68,8 @@
     private Context mContext;
     private NavigatorProvider mNavigatorProvider;
 
-    public NavInflater(@NonNull Context c, @NonNull NavigatorProvider navigatorProvider) {
-        mContext = c;
+    public NavInflater(@NonNull Context context, @NonNull NavigatorProvider navigatorProvider) {
+        mContext = context;
         mNavigatorProvider = navigatorProvider;
     }
 
@@ -102,6 +103,7 @@
      * @return
      */
     @SuppressLint("ResourceType")
+    @NonNull
     public NavGraph inflate(@NavigationRes int graphResId) {
         Resources res = mContext.getResources();
         XmlResourceParser parser = res.getXml(graphResId);
@@ -132,8 +134,9 @@
         }
     }
 
-    private NavDestination inflate(Resources res, XmlResourceParser parser, AttributeSet attrs)
-            throws XmlPullParserException, IOException {
+    @NonNull
+    private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
+            @NonNull AttributeSet attrs) throws XmlPullParserException, IOException {
         Navigator navigator = mNavigatorProvider.getNavigator(parser.getName());
         final NavDestination dest = navigator.createDestination();
 
@@ -173,8 +176,8 @@
         return dest;
     }
 
-    private void inflateArgument(Resources res, NavDestination dest, AttributeSet attrs)
-            throws XmlPullParserException {
+    private void inflateArgument(@NonNull Resources res, @NonNull NavDestination dest,
+            @NonNull AttributeSet attrs) throws XmlPullParserException {
         final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavArgument);
         String name = a.getString(R.styleable.NavArgument_android_name);
 
@@ -210,7 +213,8 @@
         a.recycle();
     }
 
-    private void inflateDeepLink(Resources res, NavDestination dest, AttributeSet attrs) {
+    private void inflateDeepLink(@NonNull Resources res, @NonNull NavDestination dest,
+            @NonNull AttributeSet attrs) {
         final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavDeepLink);
         String uri = a.getString(R.styleable.NavDeepLink_uri);
         if (TextUtils.isEmpty(uri)) {
@@ -223,7 +227,8 @@
     }
 
     @SuppressWarnings("deprecation")
-    private void inflateAction(Resources res, NavDestination dest, AttributeSet attrs) {
+    private void inflateAction(@NonNull Resources res, @NonNull NavDestination dest,
+            @NonNull AttributeSet attrs) {
         final TypedArray a = res.obtainAttributes(attrs, R.styleable.NavAction);
         final int id = a.getResourceId(R.styleable.NavAction_android_id, 0);
         final int destId = a.getResourceId(R.styleable.NavAction_destination, 0);
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/Context.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/Context.kt
new file mode 100644
index 0000000..194a94b
--- /dev/null
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/Context.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.navigation.safe.args.generator
+
+import androidx.navigation.safe.args.generator.models.Argument
+import androidx.navigation.safe.args.generator.models.Destination
+import androidx.navigation.safe.args.generator.models.ResReference
+
+internal class Context {
+    val logger = NavLogger()
+    private var nextId = 0
+
+    fun createStubId() = ResReference("error", "id", "errorId${next()}")
+    fun createStubArg() = Argument("errorArg${next()}", NavType.STRING)
+    fun createStubDestination() = Destination(createStubId(), null, "stub",
+            emptyList(), emptyList())
+
+    private fun next() = nextId++
+}
\ No newline at end of file
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/GeneratorOutput.kt
similarity index 72%
copy from navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
copy to navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/GeneratorOutput.kt
index 0a21549..87642e5 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/GeneratorOutput.kt
@@ -16,6 +16,8 @@
 
 package androidx.navigation.safe.args.generator
 
-data class XmlContext(private val name: String, private val line: Int, private val column: Int) {
-    fun createError(errorMsg: String) = Error("Error at $name:$line:$column $errorMsg")
-}
\ No newline at end of file
+data class GeneratorOutput(val files: List<String>, val errors: List<ErrorMessage>)
+
+data class ErrorMessage(val path: String, val line: Int, val column: Int, val message: String) {
+    override fun toString() = "Error at $path:$line:$column $message"
+}
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavLogger.kt
similarity index 69%
copy from navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
copy to navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavLogger.kt
index 0a21549..0cfeab2 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavLogger.kt
@@ -16,6 +16,12 @@
 
 package androidx.navigation.safe.args.generator
 
-data class XmlContext(private val name: String, private val line: Int, private val column: Int) {
-    fun createError(errorMsg: String) = Error("Error at $name:$line:$column $errorMsg")
+class NavLogger {
+    private var messages: MutableList<ErrorMessage> = mutableListOf()
+
+    fun error(message: String, position: XmlPosition) {
+        messages.add(ErrorMessage(position.name, position.line, position.column, message))
+    }
+
+    fun allMessages(): List<ErrorMessage> = messages
 }
\ No newline at end of file
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt
index 566406f..d4c2acd 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavParser.kt
@@ -37,23 +37,29 @@
 private const val NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"
 
 internal class NavParser(
-    private val parser: XmlContextParser,
+    private val parser: XmlPositionParser,
+    private val context: Context,
     private val rFilePackage: String,
-    private val applicationId: String) {
+    private val applicationId: String
+) {
 
     companion object {
-        fun parseNavigationFile(navigationXml: File, rFilePackage: String,
-                                applicationId: String): Destination {
+        fun parseNavigationFile(
+            navigationXml: File,
+            rFilePackage: String,
+            applicationId: String,
+            context: Context
+        ): Destination {
             FileReader(navigationXml).use { reader ->
-                val parser = XmlContextParser(navigationXml.name, reader)
+                val parser = XmlPositionParser(navigationXml.path, reader, context.logger)
                 parser.traverseStartTags { true }
-                return NavParser(parser, rFilePackage, applicationId).parseDestination()
+                return NavParser(parser, context, rFilePackage, applicationId).parseDestination()
             }
         }
     }
 
     internal fun parseDestination(): Destination {
-        val context = parser.xmlContext()
+        val position = parser.xmlPosition()
         val type = parser.name()
         val name = parser.attrValue(NAMESPACE_ANDROID, ATTRIBUTE_NAME) ?: ""
         val idValue = parser.attrValue(NAMESPACE_ANDROID, ATTRIBUTE_ID)
@@ -68,26 +74,32 @@
             }
         }
 
-        val id = parseNullableId(idValue, rFilePackage, context)
+        val id = idValue?.let { parseId(idValue, rFilePackage, position) }
         val className = Destination.createName(id, name, applicationId)
         if (className == null && (actions.isNotEmpty() || args.isNotEmpty())) {
-            throw context.createError(NavParserErrors.UNNAMED_DESTINATION)
+            context.logger.error(NavParserErrors.UNNAMED_DESTINATION, position)
+            return context.createStubDestination()
         }
+
         return Destination(id, className, type, args, actions, nested)
     }
 
     private fun parseArgument(): Argument {
-        val xmlContext = parser.xmlContext()
-        val name = parser.attrValueOrThrow(NAMESPACE_ANDROID, ATTRIBUTE_NAME)
+        val xmlPosition = parser.xmlPosition()
+        val name = parser.attrValueOrError(NAMESPACE_ANDROID, ATTRIBUTE_NAME)
         val defaultValue = parser.attrValue(NAMESPACE_ANDROID, ATTRIBUTE_DEFAULT_VALUE)
         val typeString = parser.attrValue(NAMESPACE_RES_AUTO, ATTRIBUTE_TYPE)
+        if (name == null) return context.createStubArg()
 
         if (typeString == null && defaultValue != null) {
             return inferArgument(name, defaultValue, rFilePackage)
         }
 
         val type = NavType.from(typeString)
-            ?: throw xmlContext.createError(NavParserErrors.unknownType(typeString))
+        if (type == null) {
+            context.logger.error(NavParserErrors.unknownType(typeString), xmlPosition)
+            return context.createStubArg()
+        }
 
         if (defaultValue == null) {
             return Argument(name, type, null)
@@ -108,25 +120,44 @@
                 NavType.REFERENCE -> NavParserErrors.invalidDefaultValueReference(defaultValue)
                 else -> NavParserErrors.invalidDefaultValue(defaultValue, type)
             }
-            throw xmlContext.createError(errorMessage)
+            context.logger.error(errorMessage, xmlPosition)
+            return context.createStubArg()
         }
 
         return Argument(name, type, defaultTypedValue)
     }
 
     private fun parseAction(): Action {
-        val idValue = parser.attrValueOrThrow(NAMESPACE_ANDROID, ATTRIBUTE_ID)
+        val idValue = parser.attrValueOrError(NAMESPACE_ANDROID, ATTRIBUTE_ID)
         val destValue = parser.attrValue(NAMESPACE_RES_AUTO, ATTRIBUTE_DESTINATION)
         val args = mutableListOf<Argument>()
-        val context = parser.xmlContext()
+        val position = parser.xmlPosition()
         parser.traverseInnerStartTags {
             if (parser.name() == TAG_ARGUMENT) {
                 args.add(parseArgument())
             }
         }
-        val id = parseId(idValue, rFilePackage) ?:
-            throw context.createError(NavParserErrors.invalidId(idValue))
-        return Action(id, parseNullableId(destValue, rFilePackage, context), args)
+
+        val id = if (idValue != null) {
+            parseId(idValue, rFilePackage, position)
+        } else {
+            context.createStubId()
+        }
+        val destination = destValue?.let { parseId(destValue, rFilePackage, position) }
+        return Action(id, destination, args)
+    }
+
+    private fun parseId(
+        xmlId: String,
+        rFilePackage: String,
+        xmlPosition: XmlPosition
+    ): ResReference {
+        val ref = parseReference(xmlId, rFilePackage)
+        if (ref?.isId() == true) {
+            return ref
+        }
+        context.logger.error(NavParserErrors.invalidId(xmlId), xmlPosition)
+        return context.createStubId()
     }
 }
 
@@ -162,19 +193,6 @@
     return ResReference(packageName, resType, resourceName)
 }
 
-internal fun parseId(xmlId: String, rFilePackage: String): ResReference? {
-    val ref = parseReference(xmlId, rFilePackage)
-    if (ref?.isId() == true) {
-        return ref
-    }
-    return null
-}
-
-internal fun parseNullableId(xmlId: String?, rFilePackage: String,
-                             context: XmlContext): ResReference? = xmlId?.let {
-    parseId(it, rFilePackage) ?: throw context.createError(NavParserErrors.invalidId(xmlId))
-}
-
 internal fun parseIntValue(value: String): IntValue? {
     try {
         if (value.startsWith("0x")) {
@@ -189,7 +207,7 @@
 }
 
 private fun parseFloatValue(value: String): FloatValue? =
-    value.toFloatOrNull()?.let { FloatValue(value) }
+        value.toFloatOrNull()?.let { FloatValue(value) }
 
 private fun parseBoolean(value: String): BooleanValue? {
     if (value == "true" || value == "false") {
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavSafeArgsGenerator.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavSafeArgsGenerator.kt
index 315bc6e..68905fe 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavSafeArgsGenerator.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/NavSafeArgsGenerator.kt
@@ -21,9 +21,14 @@
 import java.io.File
 
 fun generateSafeArgs(
-        rFilePackage: String, applicationId: String,
-        navigationXml: File, outputDir: File): List<String> {
-    val rawDestination = NavParser.parseNavigationFile(navigationXml, rFilePackage, applicationId)
+    rFilePackage: String,
+    applicationId: String,
+    navigationXml: File,
+    outputDir: File
+): GeneratorOutput {
+    val context = Context()
+    val rawDestination = NavParser.parseNavigationFile(navigationXml, rFilePackage, applicationId,
+            context)
     val resolvedDestination = resolveArguments(rawDestination)
     val javaFiles = mutableSetOf<JavaFile>()
     fun writeJavaFiles(destination: Destination) {
@@ -37,5 +42,6 @@
     }
     writeJavaFiles(resolvedDestination)
     javaFiles.forEach { javaFile -> javaFile.writeTo(outputDir) }
-    return javaFiles.map { javaFile -> "${javaFile.packageName}.${javaFile.typeSpec.name}" }
+    val files = javaFiles.map { javaFile -> "${javaFile.packageName}.${javaFile.typeSpec.name}" }
+    return GeneratorOutput(files, context.logger.allMessages())
 }
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlPosition.kt
similarity index 77%
rename from navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
rename to navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlPosition.kt
index 0a21549..bf92bae 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContext.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlPosition.kt
@@ -16,6 +16,4 @@
 
 package androidx.navigation.safe.args.generator
 
-data class XmlContext(private val name: String, private val line: Int, private val column: Int) {
-    fun createError(errorMsg: String) = Error("Error at $name:$line:$column $errorMsg")
-}
\ No newline at end of file
+data class XmlPosition(val name: String, val line: Int, val column: Int)
\ No newline at end of file
diff --git a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContextParser.kt b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlPositionParser.kt
similarity index 84%
rename from navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContextParser.kt
rename to navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlPositionParser.kt
index 1257c7d..305c54f 100644
--- a/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlContextParser.kt
+++ b/navigation/safe-args-generator/src/main/kotlin/androidx/navigation/safe/args/generator/XmlPositionParser.kt
@@ -20,7 +20,7 @@
 import org.xmlpull.v1.XmlPullParserFactory
 import java.io.Reader
 
-internal class XmlContextParser(private val name: String, reader: Reader) {
+internal class XmlPositionParser(private val name: String, reader: Reader, val logger: NavLogger) {
     private var startLine = 0
     private var startColumn = 0
     private val parser: XmlPullParser = XmlPullParserFactory.newInstance().newPullParser().apply {
@@ -53,7 +53,7 @@
         parser.nextToken()
     }
 
-    fun xmlContext() = XmlContext(name, startLine, startColumn - 1)
+    fun xmlPosition() = XmlPosition(name, startLine, startColumn - 1)
 
     fun traverseInnerStartTags(onStartTag: () -> Unit) {
         val innerDepth = parser.depth + 1
@@ -71,9 +71,13 @@
             parser.getAttributeNamespace(it) == namespace && name == parser.getAttributeName(it)
         }?.let { parser.getAttributeValue(it) }
 
-    fun attrValueOrThrow(namespace: String, attrName: String): String =
-        attrValue(namespace, attrName) ?:
-        throw xmlContext().createError(mandatoryAttrMissingError(name(), attrName))
+    fun attrValueOrError(namespace: String, attrName: String): String? {
+        val value = attrValue(namespace, attrName)
+        if (value == null) {
+            logger.error(mandatoryAttrMissingError(name(), attrName), xmlPosition())
+        }
+        return value
+    }
 }
 
 internal fun mandatoryAttrMissingError(tag: String, attr: String) =
diff --git a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/InvalidXmlTest.kt b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/InvalidXmlTest.kt
index a60bdec..639a249 100644
--- a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/InvalidXmlTest.kt
+++ b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/InvalidXmlTest.kt
@@ -22,42 +22,35 @@
 import androidx.navigation.safe.args.generator.NavParserErrors.invalidId
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
-import org.junit.Assert
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
 @RunWith(Parameterized::class)
-class InvalidXmlTest(private val testCase: TestCase) {
-
-    data class TestCase(val name: String, val line: Int, val column: Int, val errorMsg: String)
-
+class InvalidXmlTest(private val testCase: ErrorMessage) {
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "({0})")
         fun data() = listOf(
-            TestCase("unnamed_destination_with_action.xml", 25, 5, UNNAMED_DESTINATION),
-            TestCase("invalid_default_value_reference.xml", 23, 9,
+            ErrorMessage("unnamed_destination_with_action.xml", 25, 5, UNNAMED_DESTINATION),
+            ErrorMessage("invalid_default_value_reference.xml", 23, 9,
                 invalidDefaultValueReference("foo/")),
-            TestCase("invalid_default_value_int.xml", 24, 9,
+            ErrorMessage("invalid_default_value_int.xml", 24, 9,
                 invalidDefaultValue("101034f", NavType.INT)),
-            TestCase("invalid_id_action.xml", 22, 14, invalidId("@+fppid/finish")),
-            TestCase("invalid_id_destination.xml", 17, 1, invalidId("@1234234+id/foo")),
-            TestCase("action_no_id.xml", 22, 5, mandatoryAttrMissingError("action", "id"))
+            ErrorMessage("invalid_id_action.xml", 22, 44, invalidId("@+fppid/finish")),
+            ErrorMessage("invalid_id_destination.xml", 17, 1, invalidId("@1234234+id/foo")),
+            ErrorMessage("action_no_id.xml", 22, 5, mandatoryAttrMissingError("action", "id"))
         )
     }
 
     @Test
     fun invalidXml() {
-        val expectedErrorMsg = XmlContext(testCase.name, testCase.line, testCase.column)
-            .createError(testCase.errorMsg).message
-        try {
-            NavParser.parseNavigationFile(testData("invalid_xmls/${testCase.name}"),
-                "a.b", "foo.app")
-            Assert.fail()
-        } catch (e: Error) {
-            assertThat(e.message, `is`(expectedErrorMsg))
-        }
+        val context = Context()
+        val navigationXml = testData("invalid_xmls/${testCase.path}")
+        val expectedError = testCase.copy(path = navigationXml.path)
+        NavParser.parseNavigationFile(navigationXml, "a.b", "foo.app", context)
+        val messages = context.logger.allMessages()
+        assertThat(messages.size, `is`(1))
+        assertThat(messages.first(), `is`(expectedError))
     }
 }
-
diff --git a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavGeneratorTest.kt b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavGeneratorTest.kt
index 8bbc7d6..32b1664 100644
--- a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavGeneratorTest.kt
+++ b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavGeneratorTest.kt
@@ -28,21 +28,22 @@
 @RunWith(JUnit4::class)
 class NavGeneratorTest {
 
-    @Suppress("MemberVisibilityCanPrivate")
+    @Suppress("MemberVisibilityCanBePrivate")
     @get:Rule
     val workingDir = TemporaryFolder()
 
     @Test
     fun test() {
-        val javaNames = generateSafeArgs("foo", "foo.flavor",
+        val output = generateSafeArgs("foo", "foo.flavor",
             testData("naive_test.xml"), workingDir.root)
-
+        val javaNames = output.files
         val expectedSet = setOf(
                 "androidx.navigation.testapp.MainFragmentDirections",
                 "foo.flavor.NextFragmentDirections",
                 "androidx.navigation.testapp.MainFragmentArgs",
                 "foo.flavor.NextFragmentArgs"
                 )
+        assertThat(output.errors.isEmpty(), `is`(true))
         assertThat(javaNames.toSet(), `is`(expectedSet))
         javaNames.forEach { name ->
             val file = File(workingDir.root, "${name.replace('.', File.separatorChar)}.java")
diff --git a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt
index 54b3184..2412c76 100644
--- a/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt
+++ b/navigation/safe-args-generator/src/tests/kotlin/androidx/navigation/safe/args/generator/NavParserTest.kt
@@ -40,7 +40,7 @@
     fun test() {
         val id: (String) -> ResReference = { id -> ResReference("a.b", "id", id) }
         val navGraph = NavParser.parseNavigationFile(testData("naive_test.xml"),
-            "a.b", "foo.app")
+            "a.b", "foo.app", Context())
 
         val nameFirst = ClassName.get("androidx.navigation.testapp", "MainFragment")
         val nameNext = ClassName.get("foo.app", "NextFragment")
diff --git a/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_int.xml b/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_int.xml
index f795d1b..597f2e8 100644
--- a/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_int.xml
+++ b/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_int.xml
@@ -19,7 +19,7 @@
             xmlns:tools="http://schemas.android.com/tools"
             android:id="@+id/foo"
             app:startDestination="@+id/first_screen">
-    <fragment>
+    <fragment android:name="a.FakeFragment">
 
         <argument android:name="myarg1" android:defaultValue="101034f"  app:type="integer" />
     </fragment>
diff --git a/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_reference.xml b/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_reference.xml
index 22cdb8a..d5225e7 100644
--- a/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_reference.xml
+++ b/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_default_value_reference.xml
@@ -19,7 +19,7 @@
             xmlns:tools="http://schemas.android.com/tools"
             android:id="@+id/foo"
             app:startDestination="@+id/first_screen">
-    <fragment>
+    <fragment android:name="a.FakeFragment">
         <argument android:name="myarg1" android:defaultValue="foo/"  app:type="reference" />
     </fragment>
 </navigation>
\ No newline at end of file
diff --git a/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_id_action.xml b/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_id_action.xml
index d8ea53a..2e2b5e6 100644
--- a/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_id_action.xml
+++ b/navigation/safe-args-generator/src/tests/test-data/invalid_xmls/invalid_id_action.xml
@@ -19,6 +19,6 @@
             xmlns:tools="http://schemas.android.com/tools"
             android:id="@+id/foo"
             app:startDestination="@+id/first_screen">
-    <fragment><action android:id="@+fppid/finish" app:popUpTo="@id/first_screen"  />
+    <fragment android:name="a.FakeFragment"><action android:id="@+fppid/finish" app:popUpTo="@id/first_screen"  />
     </fragment>
 </navigation>
\ No newline at end of file
diff --git a/navigation/safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/ArgumentsGenerationTask.kt b/navigation/safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/ArgumentsGenerationTask.kt
index ee71478..fe91fe7 100644
--- a/navigation/safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/ArgumentsGenerationTask.kt
+++ b/navigation/safe-args-gradle-plugin/src/main/kotlin/androidx/navigation/safeargs/gradle/ArgumentsGenerationTask.kt
@@ -16,6 +16,7 @@
 
 package androidx.navigation.safeargs.gradle
 
+import androidx.navigation.safe.args.generator.ErrorMessage
 import androidx.navigation.safe.args.generator.generateSafeArgs
 import com.android.build.gradle.internal.tasks.IncrementalTask
 import com.android.ide.common.resources.FileStatus
@@ -43,9 +44,9 @@
     var navigationFiles: List<File> = emptyList()
 
     private fun generateArgs(navFiles: Collection<File>, out: File) = navFiles.map { file ->
-        Mapping(file.relativeTo(project.projectDir).path,
-                generateSafeArgs(rFilePackage, applicationId, file, out))
-    }
+        val output = generateSafeArgs(rFilePackage, applicationId, file, out)
+        Mapping(file.relativeTo(project.projectDir).path, output.files) to output.errors
+    }.unzip().let { (mappings, errorLists) -> mappings to errorLists.flatten() }
 
     private fun writeMappings(mappings: List<Mapping>) {
         File(incrementalFolder, MAPPING_FILE).writer().use { Gson().toJson(mappings, it) }
@@ -68,15 +69,16 @@
         if (!outputDir.exists() && !outputDir.mkdirs()) {
             throw GradleException("Failed to create directory for navigation arguments")
         }
-        val mappings = generateArgs(navigationFiles, outputDir)
+        val (mappings, errors) = generateArgs(navigationFiles, outputDir)
         writeMappings(mappings)
+        failIfErrors(errors)
     }
 
     override fun doIncrementalTaskAction(changedInputs: MutableMap<File, FileStatus>) {
         super.doIncrementalTaskAction(changedInputs)
         val oldMapping = readMappings()
         val navFiles = changedInputs.filter { (_, status) -> status != FileStatus.REMOVED }.keys
-        val newMapping = generateArgs(navFiles, outputDir)
+        val (newMapping, errors) = generateArgs(navFiles, outputDir)
         val newJavaFiles = newMapping.flatMap { it.javaFiles }.toSet()
         val (modified, unmodified) = oldMapping.partition {
             File(project.projectDir, it.navFile) in changedInputs
@@ -91,9 +93,21 @@
                     }
                 }
         writeMappings(unmodified + newMapping)
+        failIfErrors(errors)
+    }
+
+    private fun failIfErrors(errors: List<ErrorMessage>) {
+        if (errors.isNotEmpty()) {
+            val errString = errors.joinToString("\n") { it.toClickableText() }
+            throw GradleException(
+                    "androidx.navigation.safeargs plugin failed.\n " +
+                            "Following errors found: \n$errString")
+        }
     }
 
     override fun isIncremental() = true
 }
 
+private fun ErrorMessage.toClickableText() = "$path:$line:$column: error: $message"
+
 private data class Mapping(val navFile: String, val javaFiles: List<String>)
diff --git a/navigation/safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/PluginTest.kt b/navigation/safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/PluginTest.kt
index 9ee5f78..13283b4 100644
--- a/navigation/safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/PluginTest.kt
+++ b/navigation/safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/PluginTest.kt
@@ -46,7 +46,7 @@
 @RunWith(JUnit4::class)
 class PluginTest {
 
-    @Suppress("MemberVisibilityCanPrivate")
+    @Suppress("MemberVisibilityCanBePrivate")
     @get:Rule
     val testProjectDir = TemporaryFolder()
 
@@ -68,8 +68,11 @@
 
     private fun navResource(name: String) = File(projectRoot(), "$NAV_RESOURCES/$name")
 
-    private fun runGradle(vararg args: String) = GradleRunner.create()
-            .withProjectDir(projectRoot()).withPluginClasspath().withArguments(*args).build()
+    private fun gradleBuilder(vararg args: String) = GradleRunner.create()
+            .withProjectDir(projectRoot()).withPluginClasspath().withArguments(*args)
+
+    private fun runGradle(vararg args: String) = gradleBuilder(*args).build()
+    private fun runAndFailGradle(vararg args: String) = gradleBuilder(*args).buildAndFail()
 
     @Before
     fun setup() {
@@ -208,6 +211,42 @@
         // but additional directions are removed
         assertNotGenerated("debug/$ADDITIONAL_DIRECTIONS")
     }
+
+    @Test
+    fun invalidModify() {
+        setupSimpleBuildGradle()
+        testData("incremental-test-data/add_nav.xml").copyTo(navResource("add_nav.xml"))
+        runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
+        val step1MainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
+        val step1AdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
+        assertGenerated("debug/$NEXT_DIRECTIONS")
+
+        testData("invalid/failing_nav.xml").copyTo(navResource("nav_test.xml"), true)
+        Thread.sleep(SEC)
+        runAndFailGradle("generateSafeArgsDebug").assertFailingTask("generateSafeArgsDebug")
+        val step2MainLastMod = assertGenerated("debug/$MAIN_DIRECTIONS").lastModified()
+        // main directions were regenerated
+        assertThat(step2MainLastMod, not(step1MainLastMod))
+
+        // but additional directions weren't touched
+        val step2AdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
+        assertThat(step2AdditionalLastMod, `is`(step1AdditionalLastMod))
+
+        val step2ModifiedTime = assertGenerated("debug/$MODIFIED_NEXT_DIRECTIONS").lastModified()
+        assertNotGenerated("debug/$NEXT_DIRECTIONS")
+
+        testData("incremental-test-data/modified_nav.xml").copyTo(navResource("nav_test.xml"), true)
+        Thread.sleep(SEC)
+        runGradle("generateSafeArgsDebug").assertSuccessfulTask("generateSafeArgsDebug")
+
+        // additional directions are touched because once task failed,
+        // gradle next time makes full run
+        val step3AdditionalLastMod = assertGenerated("debug/$ADDITIONAL_DIRECTIONS").lastModified()
+        assertThat(step3AdditionalLastMod, not(step2AdditionalLastMod))
+
+        val step3ModifiedTime = assertGenerated("debug/$MODIFIED_NEXT_DIRECTIONS").lastModified()
+        assertThat(step2ModifiedTime, not(step3ModifiedTime))
+    }
 }
 
 private fun testData(name: String) = File("src/test/test-data", name)
@@ -215,4 +254,9 @@
 private fun BuildResult.assertSuccessfulTask(name: String): BuildResult {
     assertThat(task(":$name")!!.outcome, `is`(TaskOutcome.SUCCESS))
     return this
+}
+
+private fun BuildResult.assertFailingTask(name: String): BuildResult {
+    assertThat(task(":$name")!!.outcome, `is`(TaskOutcome.FAILED))
+    return this
 }
\ No newline at end of file
diff --git a/navigation/safe-args-gradle-plugin/src/test/test-data/invalid/failing_nav.xml b/navigation/safe-args-gradle-plugin/src/test/test-data/invalid/failing_nav.xml
new file mode 100644
index 0000000..990eef3
--- /dev/null
+++ b/navigation/safe-args-gradle-plugin/src/test/test-data/invalid/failing_nav.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            xmlns:tools="http://schemas.android.com/tools"
+            app:startDestination="@+id/first_screen">
+    <fragment android:id="@+id/first_screen"
+              android:name="androidx.navigation.testapp.MainFragment">
+        <argument android:name="myarg1" android:defaultValue="one" />
+        <action android:id="@+id/next" app:destination="@+id/next_fragment">
+            <argument android:name="randomArgument"/>
+        </action>
+    </fragment>
+    <fragment android:id="@+id/next_fragment"
+              android:name="androidx.navigation.testapp.ModifiedNextFragment">
+        <action android:id="@+id/next" app:destination="@+id/first_screen"/>
+        <action app:popUpTo="@id/first_screen" />
+    </fragment>
+</navigation>
\ No newline at end of file
diff --git a/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java b/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java
index 68a4994..5f5a863 100644
--- a/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java
+++ b/navigation/testing/src/main/java/androidx/navigation/testing/TestNavigator.java
@@ -18,6 +18,7 @@
 
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import android.support.v4.util.Pair;
 
 import androidx.navigation.NavDestination;
@@ -41,8 +42,8 @@
     }
 
     @Override
-    public void navigate(@NonNull Destination destination, Bundle args,
-            NavOptions navOptions) {
+    public void navigate(@NonNull Destination destination, @Nullable Bundle args,
+            @Nullable NavOptions navOptions) {
         if (navOptions != null && navOptions.shouldLaunchSingleTop() && !mBackStack.isEmpty()
                 && mBackStack.peekLast().first.getId() == destination.getId()) {
             mBackStack.pop();
diff --git a/navigation/ui/src/main/java/androidx/navigation/ui/NavigationUI.java b/navigation/ui/src/main/java/androidx/navigation/ui/NavigationUI.java
index 8d069ba..b0dd187 100644
--- a/navigation/ui/src/main/java/androidx/navigation/ui/NavigationUI.java
+++ b/navigation/ui/src/main/java/androidx/navigation/ui/NavigationUI.java
@@ -18,6 +18,7 @@
 
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.support.annotation.IdRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.widget.BottomNavigationView;
@@ -35,6 +36,7 @@
 
 import androidx.navigation.NavController;
 import androidx.navigation.NavDestination;
+import androidx.navigation.NavGraph;
 import androidx.navigation.NavOptions;
 
 /**
@@ -74,7 +76,7 @@
                 .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                 .setPopExitAnim(R.anim.nav_default_pop_exit_anim);
         if (popUp) {
-            builder.setPopUpTo(navController.getGraph().getStartDestination(), false);
+            builder.setPopUpTo(findStartDestination(navController.getGraph()).getId(), false);
         }
         NavOptions options = builder.build();
         try {
@@ -99,8 +101,8 @@
      */
     public static boolean navigateUp(@Nullable DrawerLayout drawerLayout,
             @NonNull NavController navController) {
-        if (drawerLayout != null && navController.getCurrentDestination().getId()
-                == navController.getGraph().getStartDestination()) {
+        if (drawerLayout != null && navController.getCurrentDestination()
+                == findStartDestination(navController.getGraph())) {
             drawerLayout.openDrawer(GravityCompat.START);
             return true;
         } else {
@@ -183,11 +185,10 @@
             @Override
             public void onNavigated(@NonNull NavController controller,
                     @NonNull NavDestination destination) {
-                int destinationId = destination.getId();
                 Menu menu = navigationView.getMenu();
                 for (int h = 0, size = menu.size(); h < size; h++) {
                     MenuItem item = menu.getItem(h);
-                    item.setChecked(item.getItemId() == destinationId);
+                    item.setChecked(matchDestination(destination, item.getItemId()));
                 }
             }
         });
@@ -219,11 +220,10 @@
             @Override
             public void onNavigated(@NonNull NavController controller,
                     @NonNull NavDestination destination) {
-                int destinationId = destination.getId();
                 Menu menu = bottomNavigationView.getMenu();
                 for (int h = 0, size = menu.size(); h < size; h++) {
                     MenuItem item = menu.getItem(h);
-                    if (item.getItemId() == destinationId) {
+                    if (matchDestination(destination, item.getItemId())) {
                         item.setChecked(true);
                     }
                 }
@@ -232,6 +232,33 @@
     }
 
     /**
+     * Determines whether the given <code>destId</code> matches the NavDestination. This handles
+     * both the default case (the destination's id matches the given id) and the nested case where
+     * the given id is a parent/grandparent/etc of the destination.
+     */
+    private static boolean matchDestination(@NonNull NavDestination destination,
+            @IdRes int destId) {
+        NavDestination currentDestination = destination;
+        while (currentDestination.getId() != destId && currentDestination.getParent() != null) {
+            currentDestination = currentDestination.getParent();
+        }
+        return currentDestination.getId() == destId;
+    }
+
+    /**
+     * Finds the actual start destination of the graph, handling cases where the graph's starting
+     * destination is itself a NavGraph.
+     */
+    private static NavDestination findStartDestination(@NonNull NavGraph graph) {
+        NavDestination startDestination = graph;
+        while (startDestination instanceof NavGraph) {
+            NavGraph parent = (NavGraph) startDestination;
+            startDestination = parent.findNode(parent.getStartDestination());
+        }
+        return startDestination;
+    }
+
+    /**
      * The OnNavigatedListener specifically for keeping the ActionBar updated. This handles both
      * updating the title and updating the Up Indicator transitioning between the
      */
@@ -256,8 +283,7 @@
             if (!TextUtils.isEmpty(title)) {
                 actionBar.setTitle(title);
             }
-            boolean isStartDestination =
-                    controller.getGraph().getStartDestination() == destination.getId();
+            boolean isStartDestination = findStartDestination(controller.getGraph()) == destination;
             actionBar.setDisplayHomeAsUpEnabled(mDrawerLayout != null || !isStartDestination);
             setActionBarUpIndicator(mDrawerLayout != null && isStartDestination);
         }
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java b/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java
index dc806c6..c52df80 100644
--- a/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java
+++ b/preference/src/androidTest/java/androidx/preference/tests/PreferenceIconSpaceTest.java
@@ -24,6 +24,7 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
 import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 import android.view.ViewGroup;
@@ -39,6 +40,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@SdkSuppress(maxSdkVersion = 27) // This test only works pre-P due to mocking final methods.
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class PreferenceIconSpaceTest {
diff --git a/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java b/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java
index 409a4e8..941bc59 100644
--- a/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java
+++ b/preference/src/androidTest/java/androidx/preference/tests/PreferenceSingleLineTitleTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.when;
 
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.ViewGroup;
@@ -36,6 +37,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+@SdkSuppress(maxSdkVersion = 27) // This test only works pre-P due to mocking final methods.
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class PreferenceSingleLineTitleTest {
diff --git a/room/runtime/proguard-rules.pro b/room/runtime/proguard-rules.pro
index c148df3..57eff04 100644
--- a/room/runtime/proguard-rules.pro
+++ b/room/runtime/proguard-rules.pro
@@ -1,2 +1,2 @@
--keep public class * extends androidx.room.RoomDatabase
--dontwarn androidx.room.paging.**
\ No newline at end of file
+-keep class * extends androidx.room.RoomDatabase
+-dontwarn androidx.room.paging.**
diff --git a/work/integration-tests/testapp/build.gradle b/work/integration-tests/testapp/build.gradle
index 9840d31..7c51a4b 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.0.0"
-    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
+    implementation "android.arch.persistence.room:runtime:1.1.0"
+    annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
     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/InfiniteWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java
index 07eca84..ad591d6 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/InfiniteWorker.java
@@ -22,12 +22,12 @@
 
 public class InfiniteWorker extends Worker {
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         while (true) {
             try {
                 Thread.sleep(5000L);
             } catch (InterruptedException e) {
-                return WorkerResult.RETRY;
+                return Result.RETRY;
             } finally {
                 Log.e("InfiniteWorker", "work work");
             }
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java
index bffb6b5..c720c22 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/SleepWorker.java
@@ -27,9 +27,8 @@
 public class SleepWorker extends Worker {
     private static final String TAG = "SleepWorker";
 
-    @NonNull
     @Override
-    public WorkerResult doWork() {
+    public @NonNull Result doWork() {
         try {
             Log.i(TAG, "Sleeping");
             Thread.sleep(200);
@@ -37,6 +36,6 @@
         } catch (InterruptedException ignore) {
             Log.v(TAG, "Interrupted.");
         }
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java
index 9ea45fa..a20e3f9 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/TestWorker.java
@@ -24,9 +24,9 @@
  */
 public class TestWorker extends Worker {
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         int x = 0;
         x++;
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
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 cafdec9..37cd7fc 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
@@ -43,7 +43,7 @@
     }
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Data input = getInputData();
         final String message = input.getString(ARG_MESSAGE, "completed!");
         new Handler(Looper.getMainLooper()).post(new Runnable() {
@@ -53,6 +53,6 @@
                 Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
             }
         });
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java
index 92d9948..b6e435a 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/imageprocessing/ImageCleanupWorker.java
@@ -34,7 +34,7 @@
     private static final String TAG = "ImageProcessingWorker";
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d(TAG, "Started");
         List<Image> images = TestDatabase.getInstance(getApplicationContext())
                 .getImageDao().getImages();
@@ -51,6 +51,6 @@
         }
         TestDatabase.getInstance(getApplicationContext()).getImageDao().clear();
         Log.d(TAG, "Cleanup Complete!");
-        return WorkerResult.SUCCESS;
+        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 919fe55..0fa1c5b 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
@@ -44,20 +44,20 @@
     private static final String TAG = "ImageProcessingWorker";
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d(TAG, "Started");
 
         String uriString = getInputData().getString(URI_KEY, null);
         if (TextUtils.isEmpty(uriString)) {
             Log.e(TAG, "Invalid URI!");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
 
         Bitmap image = retrieveImage(uriString);
 
         if (image == null) {
             Log.e(TAG, "Could not retrieve image!");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
 
         invertColors(image);
@@ -65,7 +65,7 @@
 
         if (TextUtils.isEmpty(filePath)) {
             Log.e(TAG, "Could not compress image!");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
 
         int processed = TestDatabase.getInstance(getApplicationContext())
@@ -74,11 +74,11 @@
 
         if (processed != 1) {
             Log.e(TAG, "Database was not updated!");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
 
         Log.d(TAG, "Image Processing Complete!");
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 
     private Bitmap retrieveImage(String uriString) {
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 fee80dd..304e7fe 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
@@ -34,20 +34,20 @@
     private static final String URI_KEY = "uri";
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d(TAG, "Started");
 
         String uriString = getInputData().getString(URI_KEY, null);
         if (TextUtils.isEmpty(uriString)) {
             Log.e(TAG, "Invalid URI!");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
 
         Image image = new Image();
         image.mOriginalAssetName = uriString;
         image.mIsProcessed = false;
         TestDatabase.getInstance(getApplicationContext()).getImageDao().insert(image);
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 
     static OneTimeWorkRequest createWork(String uriString) {
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 4ed0cdd..fcc7540 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
@@ -55,7 +55,7 @@
     }
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Data input = getInputData();
         String inputFileName = input.getString(INPUT_FILE, null);
         String outputFileName = "out_" + inputFileName;
@@ -77,7 +77,7 @@
                 }
             }
         } catch (IOException e) {
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         } finally {
             if (scanner != null) {
                 scanner.close();
@@ -102,7 +102,7 @@
                 dataOutputStream.writeInt(entry.getValue());
             }
         } catch (IOException e) {
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         } finally {
             if (dataOutputStream != null) {
                 try {
@@ -123,6 +123,6 @@
         setOutputData(new Data.Builder().putString(INPUT_FILE, outputFileName).build());
 
         Log.d("Map", "Mapping finished for " + inputFileName);
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java
index 6dddb61..3187a46 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextReducingWorker.java
@@ -42,7 +42,7 @@
     private Map<String, Integer> mWordCount = new HashMap<>();
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Data input = getInputData();
         String[] inputFiles = input.getStringArray(INPUT_FILE);
         if (inputFiles == null) {
@@ -64,7 +64,7 @@
                     mWordCount.put(word, count);
                 }
             } catch (IOException e) {
-                return WorkerResult.FAILURE;
+                return Result.FAILURE;
             } finally {
                 if (dataInputStream != null) {
                     try {
@@ -102,6 +102,6 @@
         }
 
         Log.d("Reduce", "Reduction finished");
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java
index 4655565..4479c70 100644
--- a/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java
+++ b/work/integration-tests/testapp/src/main/java/androidx/work/integration/testapp/sherlockholmes/TextStartupWorker.java
@@ -27,10 +27,10 @@
 public class TextStartupWorker extends Worker {
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         TestDatabase db = TestDatabase.getInstance(getApplicationContext());
         db.getWordCountDao().clear();
         Log.d("Startup", "Database cleared");
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseInfiniteTestWorker.java b/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseInfiniteTestWorker.java
index 72918da..fe4ac40 100644
--- a/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseInfiniteTestWorker.java
+++ b/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseInfiniteTestWorker.java
@@ -28,7 +28,7 @@
     private static final String TAG = "FBInfiniteTestWorker";
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         while (true) {
         }
     }
diff --git a/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseTestWorker.java b/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseTestWorker.java
index 6d5e917..ba2031a 100644
--- a/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseTestWorker.java
+++ b/work/workmanager-firebase/src/androidTest/java/androidx/work/worker/FirebaseTestWorker.java
@@ -28,8 +28,8 @@
 public class FirebaseTestWorker extends Worker {
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d("FirebaseTestWorker", "FirebaseTestWorker Ran!");
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/TestWorker.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/TestWorker.kt
index 6cade43..b07c8cd 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/TestWorker.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/TestWorker.kt
@@ -19,7 +19,7 @@
 import androidx.work.Worker
 
 class TestWorker : Worker() {
-    override fun doWork(): WorkerResult {
-        return WorkerResult.SUCCESS
+    override fun doWork(): Result {
+        return Result.SUCCESS
     }
 }
diff --git a/work/workmanager-test/src/androidTest/java/androidx/work/test/workers/TestWorker.java b/work/workmanager-test/src/androidTest/java/androidx/work/test/workers/TestWorker.java
index dd2834c..94ff3a0 100644
--- a/work/workmanager-test/src/androidTest/java/androidx/work/test/workers/TestWorker.java
+++ b/work/workmanager-test/src/androidTest/java/androidx/work/test/workers/TestWorker.java
@@ -24,10 +24,9 @@
 public class TestWorker extends Worker {
     private static final String TAG = "TestWorker";
 
-    @NonNull
     @Override
-    public WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.i(TAG, "Doing work.");
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager-test/src/main/java/androidx/work/test/TestScheduler.java b/work/workmanager-test/src/main/java/androidx/work/test/TestScheduler.java
index 6b9b1f8..bfec171 100644
--- a/work/workmanager-test/src/main/java/androidx/work/test/TestScheduler.java
+++ b/work/workmanager-test/src/main/java/androidx/work/test/TestScheduler.java
@@ -76,12 +76,10 @@
     }
 
     /**
-     * Tells the {@link TestScheduler} to pretend that all constraints on the
-     * {@link Worker} with the given {@code workSpecId} are met.
+     * Tells the {@link TestScheduler} to pretend that all constraints on the {@link Worker} with
+     * the given {@code workSpecId} are met.
      *
-     * The {@link Worker} is scheduled for execution.
-     *
-     * @param workSpecId is the {@link Worker}s id.
+     * @param workSpecId The {@link Worker}'s id.
      */
     void setAllConstraintsMet(@NonNull UUID workSpecId) {
         synchronized (sLock) {
diff --git a/work/workmanager-test/src/main/java/androidx/work/test/WorkManagerTestInitHelper.java b/work/workmanager-test/src/main/java/androidx/work/test/WorkManagerTestInitHelper.java
index a349865..d3e691a 100644
--- a/work/workmanager-test/src/main/java/androidx/work/test/WorkManagerTestInitHelper.java
+++ b/work/workmanager-test/src/main/java/androidx/work/test/WorkManagerTestInitHelper.java
@@ -76,8 +76,8 @@
     }
 
     /**
-     * @return An instance of {@link TestDriver}. This exposes additional functionality
-     * that are useful in the context of testing when using WorkManager.
+     * @return An instance of {@link TestDriver}. This exposes additional functionality that is
+     * useful in the context of testing when using WorkManager.
      */
     public static TestDriver getTestDriver() {
         WorkManagerImpl workManager = WorkManagerImpl.getInstance();
diff --git a/work/workmanager/build.gradle b/work/workmanager/build.gradle
index 369c807..fedc052 100644
--- a/work/workmanager/build.gradle
+++ b/work/workmanager/build.gradle
@@ -29,13 +29,24 @@
     buildTypes.all {
         consumerProguardFiles 'proguard-rules.pro'
     }
+    defaultConfig {
+        javaCompileOptions {
+            annotationProcessorOptions {
+                arguments = ["room.schemaLocation": "$projectDir/src/schemas".toString()]
+            }
+        }
+    }
+    sourceSets {
+        androidTest.assets.srcDirs += files("$projectDir/src/schemas".toString())
+    }
 }
 
 dependencies {
     api "android.arch.lifecycle:extensions:1.1.0"
-    implementation "android.arch.persistence.room:runtime:1.0.0"
-    annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
+    implementation "android.arch.persistence.room:runtime:1.1.0"
+    annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
     androidTestImplementation "android.arch.core:core-testing:1.1.0"
+    androidTestImplementation "android.arch.persistence.room:testing:1.1.0"
     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
new file mode 100644
index 0000000..1c1893f
--- /dev/null
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
+import android.arch.persistence.room.testing.MigrationTestHelper;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+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 org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.UUID;
+
+@RunWith(AndroidJUnit4.class)
+public class WorkDatabaseMigrationTest {
+
+    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";
+
+    // Queries
+    private static final String INSERT_ALARM_INFO = "INSERT INTO alarmInfo VALUES (?, ?)";
+    private static final String INSERT_SYSTEM_ID_INFO = "INSERT INTO systemIdInfo VALUES (?, ?)";
+    private static final String CHECK_SYSTEM_ID_INFO = "SELECT * FROM systemIdInfo";
+    private static final String CHECK_ALARM_INFO = "SELECT * FROM alarmInfo";
+    private static final String CHECK_TABLE_NAME = "SELECT * FROM %s";
+    private static final String TABLE_ALARM_INFO = "alarmInfo";
+    private static final String TABLE_SYSTEM_ID_INFO = "systemIdInfo";
+    private static final String TABLE_WORKSPEC = "WorkSpec";
+    private static final String TABLE_WORKTAG = "WorkTag";
+    private static final String TABLE_WORKNAME = "WorkName";
+
+    private File mDatabasePath;
+
+    @Rule
+    public MigrationTestHelper mMigrationTestHelper = new MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            WorkDatabase.class.getCanonicalName(),
+            new FrameworkSQLiteOpenHelperFactory());
+
+    @Before
+    public void setUp() {
+        // Delete the database if it exists.
+        mDatabasePath = InstrumentationRegistry.getContext().getDatabasePath(TEST_DATABASE);
+        if (mDatabasePath.exists()) {
+            mDatabasePath.delete();
+        }
+    }
+
+    @Test
+    @MediumTest
+    public void testMigrationVersion1To2() 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);
+
+        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();
+    }
+
+    private boolean checkExists(SupportSQLiteDatabase database, String tableName) {
+        Cursor cursor = null;
+        try {
+            cursor = database.query(String.format(CHECK_TABLE_NAME, tableName));
+            return true;
+        } catch (SQLiteException ignored) {
+            // Should fail with a SQLiteException (no such table: tableName)
+            return false;
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/SchedulersTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/SchedulersTest.java
index 8dbc932..d579394 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/SchedulersTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/SchedulersTest.java
@@ -21,6 +21,7 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.os.Build;
@@ -29,7 +30,6 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import androidx.work.Configuration;
 import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
 import androidx.work.impl.background.systemalarm.SystemAlarmService;
 import androidx.work.impl.background.systemjob.SystemJobScheduler;
@@ -44,19 +44,19 @@
 public class SchedulersTest {
 
     private Context mAppContext;
-    private Configuration mConfiguration;
+    private WorkManagerImpl mWorkManager;
 
     @Before
     public void setUp() {
+        mWorkManager = mock(WorkManagerImpl.class);
         mAppContext = InstrumentationRegistry.getTargetContext();
-        mConfiguration = new Configuration.Builder().build();
     }
 
     @Test
     @SdkSuppress(minSdkVersion = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
     public void testGetBackgroundScheduler_withJobSchedulerApiLevel() {
         Scheduler scheduler =
-                Schedulers.createBestAvailableBackgroundScheduler(mAppContext, mConfiguration);
+                Schedulers.createBestAvailableBackgroundScheduler(mAppContext, mWorkManager);
         assertThat(scheduler, is(instanceOf(SystemJobScheduler.class)));
         assertServicesEnabled(true, false, false);
     }
@@ -65,7 +65,7 @@
     @SdkSuppress(maxSdkVersion = WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL)
     public void testGetBackgroundScheduler_beforeJobSchedulerApiLevel() {
         Scheduler scheduler =
-                Schedulers.createBestAvailableBackgroundScheduler(mAppContext, mConfiguration);
+                Schedulers.createBestAvailableBackgroundScheduler(mAppContext, mWorkManager);
         assertThat(scheduler, is(instanceOf(SystemAlarmScheduler.class)));
         assertServicesEnabled(false, false, true);
     }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index 6317d9b..7783208 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -45,6 +45,7 @@
 import androidx.work.TestLifecycleOwner;
 import androidx.work.WorkContinuation;
 import androidx.work.WorkManagerTest;
+import androidx.work.WorkStatus;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule;
@@ -57,6 +58,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -478,6 +480,29 @@
         assertThat(joined.hasCycles(), is(false));
     }
 
+    @Test
+    @SmallTest
+    public void testGetStatusesSync() {
+        OneTimeWorkRequest aWork = createTestWorker(); // A
+        OneTimeWorkRequest bWork = createTestWorker(); // B
+        OneTimeWorkRequest cWork = createTestWorker(); // C
+        OneTimeWorkRequest dWork = createTestWorker(); // D
+
+        WorkContinuation firstChain = mWorkManagerImpl.beginWith(aWork).then(bWork);
+        WorkContinuation secondChain = mWorkManagerImpl.beginWith(cWork);
+        WorkContinuation combined = WorkContinuation.combine(dWork, firstChain, secondChain);
+
+        combined.synchronous().enqueueSync();
+        List<WorkStatus> statuses = combined.synchronous().getStatusesSync();
+        assertThat(statuses, is(notNullValue()));
+        List<UUID> ids = new ArrayList<>(statuses.size());
+        for (WorkStatus status : statuses) {
+            ids.add(status.getId());
+        }
+        assertThat(ids, containsInAnyOrder(
+                aWork.getId(), bWork.getId(), cWork.getId(), dWork.getId()));
+    }
+
     private static void verifyEnqueued(WorkContinuationImpl continuation) {
         assertThat(continuation.isEnqueued(), is(true));
         List<WorkContinuationImpl> parents = continuation.getParents();
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
new file mode 100644
index 0000000..51e98e5
--- /dev/null
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.impl;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.arch.core.executor.ArchTaskExecutor;
+import android.arch.core.executor.TaskExecutor;
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.SdkSuppress;
+import android.support.test.runner.AndroidJUnit4;
+
+import androidx.work.Configuration;
+import androidx.work.OneTimeWorkRequest;
+import androidx.work.impl.model.WorkSpec;
+import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule;
+import androidx.work.worker.TestWorker;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+
+@RunWith(AndroidJUnit4.class)
+public class WorkManagerImplLargeExecutorTest {
+
+    private static final int NUM_WORKERS = 500;
+
+    // ThreadPoolExecutor parameters.
+    private static final int MIN_POOL_SIZE = 0;
+    // Allocate more threads than the MAX_SCHEDULER_LIMIT
+    private static final int MAX_POOL_SIZE = 150;
+    // Keep alive time for a thread before its claimed.
+    private static final long KEEP_ALIVE_TIME = 2L;
+
+    private WorkManagerImpl mWorkManagerImpl;
+
+    @Rule
+    public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
+
+    @Before
+    public void setUp() {
+        ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() {
+            @Override
+            public void executeOnDiskIO(@NonNull Runnable runnable) {
+                runnable.run();
+            }
+
+            @Override
+            public void postToMainThread(@NonNull Runnable runnable) {
+                runnable.run();
+            }
+
+            @Override
+            public boolean isMainThread() {
+                return true;
+            }
+        });
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
+        Executor executor = new ThreadPoolExecutor(
+                MIN_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, SECONDS, queue);
+        Configuration configuration = new Configuration.Builder()
+                .setExecutor(executor)
+                .build();
+        mWorkManagerImpl = new WorkManagerImpl(context, configuration);
+        WorkManagerImpl.setDelegate(mWorkManagerImpl);
+    }
+
+    @After
+    public void tearDown() {
+        WorkManagerImpl.setDelegate(null);
+        ArchTaskExecutor.getInstance().setDelegate(null);
+    }
+
+    @Test
+    @LargeTest
+    @SdkSuppress(maxSdkVersion = 22)
+    public void testSchedulerLimits() {
+        for (int i = 0; i < NUM_WORKERS; i++) {
+            mWorkManagerImpl.enqueue(OneTimeWorkRequest.from(TestWorker.class));
+            List<WorkSpec> eligibleWorkSpecs = mWorkManagerImpl.getWorkDatabase()
+                    .workSpecDao()
+                    .getEligibleWorkForScheduling();
+
+            int size = eligibleWorkSpecs != null ? eligibleWorkSpecs.size() : 0;
+            assertThat(size, lessThanOrEqualTo(Scheduler.MAX_SCHEDULER_LIMIT));
+        }
+    }
+}
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 7e7abd2..c1daf54 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -39,7 +39,6 @@
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.isIn;
 import static org.hamcrest.Matchers.isOneOf;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
@@ -103,8 +102,6 @@
 @RunWith(AndroidJUnit4.class)
 public class WorkManagerImplTest {
 
-    private static final int NUM_WORKERS = 500;
-
     private WorkDatabase mDatabase;
     private WorkManagerImpl mWorkManagerImpl;
 
@@ -147,7 +144,7 @@
 
     @Test
     @SmallTest
-    public void testEnqueue_insertWork() throws InterruptedException {
+    public void testEnqueue_insertWork() {
         final int workCount = 3;
         final OneTimeWorkRequest[] workArray = new OneTimeWorkRequest[workCount];
         for (int i = 0; i < workCount; ++i) {
@@ -1266,6 +1263,29 @@
 
     @Test
     @SmallTest
+    public void testCancelAllWork() {
+        OneTimeWorkRequest work0 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        OneTimeWorkRequest work1 = new OneTimeWorkRequest.Builder(TestWorker.class).build();
+        OneTimeWorkRequest work2 = new OneTimeWorkRequest.Builder(TestWorker.class)
+                .setInitialState(SUCCEEDED)
+                .build();
+        insertWorkSpecAndTags(work0);
+        insertWorkSpecAndTags(work1);
+        insertWorkSpecAndTags(work2);
+
+        WorkSpecDao workSpecDao = mDatabase.workSpecDao();
+        assertThat(workSpecDao.getState(work0.getStringId()), is(ENQUEUED));
+        assertThat(workSpecDao.getState(work1.getStringId()), is(ENQUEUED));
+        assertThat(workSpecDao.getState(work2.getStringId()), is(SUCCEEDED));
+
+        mWorkManagerImpl.synchronous().cancelAllWorkSync();
+        assertThat(workSpecDao.getState(work0.getStringId()), is(CANCELLED));
+        assertThat(workSpecDao.getState(work1.getStringId()), is(CANCELLED));
+        assertThat(workSpecDao.getState(work2.getStringId()), is(SUCCEEDED));
+    }
+
+    @Test
+    @SmallTest
     public void testSynchronousCancelAndGetStatus() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWorkSpecAndTags(work);
@@ -1449,21 +1469,6 @@
         assertThat(workSpec.workerClassName, is(TestWorker.class.getName()));
     }
 
-    @Test
-    @LargeTest
-    @SdkSuppress(maxSdkVersion = 22)
-    public void testSchedulerLimits() {
-        for (int i = 0; i < NUM_WORKERS; i++) {
-            mWorkManagerImpl.enqueue(OneTimeWorkRequest.from(TestWorker.class));
-            List<WorkSpec> eligibleWorkSpecs = mWorkManagerImpl.getWorkDatabase()
-                    .workSpecDao()
-                    .getEligibleWorkForScheduling();
-
-            int size = eligibleWorkSpecs != null ? eligibleWorkSpecs.size() : 0;
-            assertThat(size, lessThanOrEqualTo(Scheduler.MAX_SCHEDULER_LIMIT));
-        }
-    }
-
     private void insertWorkSpecAndTags(WorkRequest work) {
         mDatabase.workSpecDao().insertWorkSpec(work.getWorkSpec());
         for (String tag : work.getTags()) {
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 a8bbb16..4a50d10 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -58,6 +58,7 @@
 import androidx.work.worker.EchoingWorker;
 import androidx.work.worker.ExceptionWorker;
 import androidx.work.worker.FailureWorker;
+import androidx.work.worker.InfiniteTestWorker;
 import androidx.work.worker.InterruptionAwareWorker;
 import androidx.work.worker.RetryWorker;
 import androidx.work.worker.SleepTestWorker;
@@ -571,7 +572,7 @@
         Worker worker = WorkerWrapper.workerFromWorkSpec(
                 mContext,
                 getWorkSpec(work),
-                new Extras(Data.EMPTY, Collections.<String>emptyList(), null));
+                new Extras(Data.EMPTY, Collections.<String>emptyList(), null, 1));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getApplicationContext(), is(equalTo(mContext.getApplicationContext())));
@@ -589,7 +590,7 @@
         Worker worker = WorkerWrapper.workerFromWorkSpec(
                 mContext,
                 getWorkSpec(work),
-                new Extras(input, Collections.<String>emptyList(), null));
+                new Extras(input, Collections.<String>emptyList(), null, 1));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getInputData().getString(key, null), is(expectedValue));
@@ -598,7 +599,7 @@
         worker = WorkerWrapper.workerFromWorkSpec(
                 mContext,
                 getWorkSpec(work),
-                new Extras(Data.EMPTY, Collections.<String>emptyList(), null));
+                new Extras(Data.EMPTY, Collections.<String>emptyList(), null, 1));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getInputData().size(), is(0));
@@ -616,7 +617,7 @@
         Worker worker = WorkerWrapper.workerFromWorkSpec(
                 mContext,
                 getWorkSpec(work),
-                new Extras(Data.EMPTY, Arrays.asList("one", "two", "three"), null));
+                new Extras(Data.EMPTY, Arrays.asList("one", "two", "three"), null, 1));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getTags(), containsInAnyOrder("one", "two", "three"));
@@ -634,7 +635,7 @@
         Worker worker = WorkerWrapper.workerFromWorkSpec(
                 mContext,
                 getWorkSpec(work),
-                new Extras(Data.EMPTY, Collections.<String>emptyList(), runtimeExtras));
+                new Extras(Data.EMPTY, Collections.<String>emptyList(), runtimeExtras, 1));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getTriggeredContentAuthorities(),
@@ -711,7 +712,7 @@
                 mContext,
                 InterruptionAwareWorker.class.getName(),
                 work.getId(),
-                new Extras(Data.EMPTY, Collections.<String>emptyList(), null));
+                new Extras(Data.EMPTY, Collections.<String>emptyList(), null, 1));
         assertThat(worker, is(notNullValue()));
         assertThat(worker.isStopped(), is(false));
 
@@ -738,7 +739,7 @@
                 mContext,
                 InterruptionAwareWorker.class.getName(),
                 work.getId(),
-                new Extras(Data.EMPTY, Collections.<String>emptyList(), null));
+                new Extras(Data.EMPTY, Collections.<String>emptyList(), null, 1));
         assertThat(worker, is(notNullValue()));
         assertThat(worker.isStopped(), is(false));
 
@@ -786,4 +787,23 @@
         Thread.sleep(6000L);
         verify(mMockListener).onExecuted(work.getStringId(), false, false);
     }
+
+    @Test
+    @LargeTest
+    public void testWorker_getsRunAttemptCount() throws InterruptedException {
+        OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class)
+                .setInitialRunAttemptCount(10)
+                .build();
+        insertWork(work);
+
+        WorkerWrapper workerWrapper =
+                new WorkerWrapper.Builder(mContext, mDatabase, work.getStringId())
+                .withSchedulers(Collections.singletonList(mMockScheduler))
+                .withListener(mMockListener)
+                .build();
+
+        Executors.newSingleThreadExecutor().submit(workerWrapper);
+        Thread.sleep(1000L);
+        assertThat(workerWrapper.mWorker.getRunAttemptCount(), is(10));
+    }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java
index 16fb69a..7bcf52e 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java
@@ -31,7 +31,7 @@
 import androidx.work.DatabaseTest;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.model.AlarmInfo;
+import androidx.work.impl.model.SystemIdInfo;
 import androidx.work.worker.TestWorker;
 
 import org.junit.Before;
@@ -64,8 +64,8 @@
         String workSpecId = work.getStringId();
 
         Alarms.setAlarm(mContext, mWorkManager, workSpecId, mTriggerAt);
-        AlarmInfo alarmInfo = mDatabase.alarmInfoDao().getAlarmInfo(workSpecId);
-        assertThat(alarmInfo, is(notNullValue()));
+        SystemIdInfo systemIdInfo = mDatabase.systemIdInfoDao().getSystemIdInfo(workSpecId);
+        assertThat(systemIdInfo, is(notNullValue()));
     }
 
     @Test
@@ -74,14 +74,13 @@
         insertWork(work);
         String workSpecId = work.getStringId();
 
-        AlarmInfo alarmInfo = new AlarmInfo(workSpecId, 1);
-
-        mDatabase.alarmInfoDao().insertAlarmInfo(alarmInfo);
+        SystemIdInfo systemIdInfo = new SystemIdInfo(workSpecId, 1);
+        mDatabase.systemIdInfoDao().insertSystemIdInfo(systemIdInfo);
 
         Alarms.setAlarm(mContext, mWorkManager, workSpecId, mTriggerAt);
-        AlarmInfo updatedAlarmInfo = mDatabase.alarmInfoDao().getAlarmInfo(workSpecId);
-        assertThat(updatedAlarmInfo, is(notNullValue()));
-        assertThat(updatedAlarmInfo.alarmId, is(alarmInfo.alarmId));
+        SystemIdInfo updatedSystemIdInfo = mDatabase.systemIdInfoDao().getSystemIdInfo(workSpecId);
+        assertThat(updatedSystemIdInfo, is(notNullValue()));
+        assertThat(updatedSystemIdInfo.systemId, is(systemIdInfo.systemId));
     }
 
     @Test
@@ -90,12 +89,11 @@
         insertWork(work);
         String workSpecId = work.getStringId();
 
-        AlarmInfo alarmInfo = new AlarmInfo(workSpecId, 1);
-
-        mDatabase.alarmInfoDao().insertAlarmInfo(alarmInfo);
+        SystemIdInfo systemIdInfo = new SystemIdInfo(workSpecId, 1);
+        mDatabase.systemIdInfoDao().insertSystemIdInfo(systemIdInfo);
 
         Alarms.cancelAlarm(mContext, mWorkManager, workSpecId);
-        AlarmInfo updatedAlarmInfo = mDatabase.alarmInfoDao().getAlarmInfo(workSpecId);
-        assertThat(updatedAlarmInfo, is(nullValue()));
+        SystemIdInfo updatedSystemIdInfo = mDatabase.systemIdInfoDao().getSystemIdInfo(workSpecId);
+        assertThat(updatedSystemIdInfo, is(nullValue()));
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
index 8d03b1f..b0d499e 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobInfoConverterTest.java
@@ -25,9 +25,6 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.arrayContaining;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
 
 import android.app.job.JobInfo;
 import android.net.Uri;
@@ -38,7 +35,6 @@
 import android.support.test.runner.AndroidJUnit4;
 
 import androidx.work.BackoffPolicy;
-import androidx.work.Configuration;
 import androidx.work.Constraints;
 import androidx.work.NetworkType;
 import androidx.work.OneTimeWorkRequest;
@@ -46,7 +42,6 @@
 import androidx.work.WorkManagerTest;
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.WorkSpec;
-import androidx.work.impl.utils.IdGenerator;
 import androidx.work.worker.TestWorker;
 
 import org.junit.Before;
@@ -61,39 +56,34 @@
             PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS + 1232L;
     private static final long TEST_FLEX_DURATION =
             PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS + 112L;
+    private static final int JOB_ID = 101;
 
-    private IdGenerator mMockIdGenerator;
     private SystemJobInfoConverter mConverter;
 
     @Before
     public void setUp() {
-        mMockIdGenerator = mock(IdGenerator.class);
         mConverter = new SystemJobInfoConverter(
-                InstrumentationRegistry.getTargetContext(),
-                new Configuration.Builder().build(),
-                mMockIdGenerator);
+                InstrumentationRegistry.getTargetContext());
     }
 
     @Test
     @SmallTest
     public void testConvert_ids() {
         final String expectedWorkSpecId = "026e3422-9cd1-11e7-abc4-cec278b6b50a";
-        final int expectedJobId = 101;
-        when(mMockIdGenerator.nextJobSchedulerIdWithRange(anyInt(), anyInt()))
-                .thenReturn(expectedJobId);
         WorkSpec workSpec = new WorkSpec(expectedWorkSpecId, TestWorker.class.getName());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         String actualWorkSpecId = jobInfo.getExtras().getString(
                 SystemJobInfoConverter.EXTRA_WORK_SPEC_ID);
         assertThat(actualWorkSpecId, is(expectedWorkSpecId));
-        assertThat(jobInfo.getId(), is(expectedJobId));
+        assertThat(jobInfo.getId(), is(JOB_ID));
     }
 
     @Test
     @SmallTest
     public void testConvert_setPersistedByDefault() {
-        JobInfo jobInfo = mConverter.convert(new WorkSpec("id", TestWorker.class.getName()));
-        assertThat(jobInfo.isPersisted(), is(true));
+        JobInfo jobInfo = mConverter.convert(
+                new WorkSpec("id", TestWorker.class.getName()), JOB_ID);
+        assertThat(jobInfo.isPersisted(), is(false));
     }
 
     /**
@@ -106,7 +96,7 @@
     @Test
     @SmallTest
     public void testConvert_noConstraints_doesNotThrowException() {
-        mConverter.convert(new WorkSpec("id", TestWorker.class.getName()));
+        mConverter.convert(new WorkSpec("id", TestWorker.class.getName()), JOB_ID);
     }
 
     @Test
@@ -116,7 +106,7 @@
         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
         workSpec.setBackoffDelayDuration(expectedBackoffDelayDuration);
         workSpec.backoffPolicy = BackoffPolicy.LINEAR;
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.getInitialBackoffMillis(), is(expectedBackoffDelayDuration));
         assertThat(jobInfo.getBackoffPolicy(), is(JobInfo.BACKOFF_POLICY_LINEAR));
     }
@@ -127,7 +117,7 @@
         final long expectedInitialDelay = 12123L;
         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
         workSpec.initialDelay = expectedInitialDelay;
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.getMinLatencyMillis(), is(expectedInitialDelay));
     }
 
@@ -136,7 +126,7 @@
     public void testConvert_periodicWithNoFlex() {
         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
         workSpec.setPeriodic(TEST_INTERVAL_DURATION);
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.getIntervalMillis(), is(TEST_INTERVAL_DURATION));
     }
 
@@ -146,7 +136,7 @@
     public void testConvert_periodicWithFlex() {
         WorkSpec workSpec = new WorkSpec("id", TestWorker.class.getName());
         workSpec.setPeriodic(TEST_INTERVAL_DURATION, TEST_FLEX_DURATION);
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.getIntervalMillis(), is(TEST_INTERVAL_DURATION));
         assertThat(jobInfo.getFlexMillis(), is(TEST_FLEX_DURATION));
     }
@@ -157,7 +147,7 @@
         final boolean expectedRequireCharging = true;
         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
                 .setRequiresCharging(expectedRequireCharging).build());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.isRequireCharging(), is(expectedRequireCharging));
     }
 
@@ -171,7 +161,7 @@
                         expectedUri, JobInfo.TriggerContentUri.FLAG_NOTIFY_FOR_DESCENDANTS);
         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
                 .addContentUriTrigger(expectedUri, true).build());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
 
         JobInfo.TriggerContentUri[] triggerContentUris = jobInfo.getTriggerContentUris();
         assertThat(triggerContentUris, is(arrayContaining(expectedTriggerContentUri)));
@@ -183,7 +173,7 @@
         final boolean expectedRequireDeviceIdle = true;
         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
                 .setRequiresDeviceIdle(expectedRequireDeviceIdle).build());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.isRequireDeviceIdle(), is(expectedRequireDeviceIdle));
     }
 
@@ -194,7 +184,7 @@
         final boolean expectedRequireBatteryNotLow = true;
         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
                 .setRequiresBatteryNotLow(expectedRequireBatteryNotLow).build());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.isRequireBatteryNotLow(), is(expectedRequireBatteryNotLow));
     }
 
@@ -205,7 +195,7 @@
         final boolean expectedRequireStorageNotLow = true;
         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
                 .setRequiresStorageNotLow(expectedRequireStorageNotLow).build());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         assertThat(jobInfo.isRequireStorageNotLow(), is(expectedRequireStorageNotLow));
     }
 
@@ -226,7 +216,7 @@
                                                 int minSdkVersion) {
         WorkSpec workSpec = getTestWorkSpecWithConstraints(new Constraints.Builder()
                 .setRequiredNetworkType(networkType).build());
-        JobInfo jobInfo = mConverter.convert(workSpec);
+        JobInfo jobInfo = mConverter.convert(workSpec, JOB_ID);
         if (Build.VERSION.SDK_INT >= minSdkVersion) {
             assertThat(jobInfo.getNetworkType(), is(jobInfoNetworkType));
         } else {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
index a8ee397..44f82d5 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobSchedulerTest.java
@@ -23,12 +23,14 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
@@ -42,7 +44,9 @@
 import androidx.work.Configuration;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.WorkManagerTest;
+import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.SystemIdInfoDao;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.worker.TestWorker;
 
@@ -59,6 +63,7 @@
 
     private static final String TEST_ID = "test";
 
+    private WorkManagerImpl mWorkManager;
     private JobScheduler mJobScheduler;
     private SystemJobScheduler mSystemJobScheduler;
 
@@ -66,7 +71,17 @@
     public void setUp() {
         Context context = InstrumentationRegistry.getTargetContext();
         Configuration configuration = new Configuration.Builder().build();
+        WorkDatabase workDatabase = mock(WorkDatabase.class);
+        SystemIdInfoDao systemIdInfoDao = mock(SystemIdInfoDao.class);
+
+
+        mWorkManager = mock(WorkManagerImpl.class);
         mJobScheduler = mock(JobScheduler.class);
+
+        when(mWorkManager.getConfiguration()).thenReturn(configuration);
+        when(workDatabase.systemIdInfoDao()).thenReturn(systemIdInfoDao);
+        when(mWorkManager.getWorkDatabase()).thenReturn(workDatabase);
+
         doReturn(RESULT_SUCCESS).when(mJobScheduler).schedule(any(JobInfo.class));
 
         List<JobInfo> allJobInfos = new ArrayList<>(2);
@@ -82,10 +97,13 @@
         doReturn(allJobInfos).when(mJobScheduler).getAllPendingJobs();
 
         mSystemJobScheduler =
-                spy(new SystemJobScheduler(mJobScheduler,
-                        new SystemJobInfoConverter(context, configuration)));
+                spy(new SystemJobScheduler(
+                        context,
+                        mWorkManager,
+                        mJobScheduler,
+                        new SystemJobInfoConverter(context)));
 
-        doNothing().when(mSystemJobScheduler).scheduleInternal(any(WorkSpec.class));
+        doNothing().when(mSystemJobScheduler).scheduleInternal(any(WorkSpec.class), anyInt());
     }
 
     @Test
@@ -100,8 +118,10 @@
 
         mSystemJobScheduler.schedule(workSpec1, workSpec2);
 
-        verify(mSystemJobScheduler, times(2)).scheduleInternal(workSpec1);
-        verify(mSystemJobScheduler, times(2)).scheduleInternal(workSpec2);
+        verify(mSystemJobScheduler, times(2))
+                .scheduleInternal(eq(workSpec1), anyInt());
+        verify(mSystemJobScheduler, times(2))
+                .scheduleInternal(eq(workSpec2), anyInt());
     }
 
     @Test
@@ -116,8 +136,10 @@
 
         mSystemJobScheduler.schedule(workSpec1, workSpec2);
 
-        verify(mSystemJobScheduler, times(1)).scheduleInternal(workSpec1);
-        verify(mSystemJobScheduler, times(1)).scheduleInternal(workSpec2);
+        verify(mSystemJobScheduler, times(1))
+                .scheduleInternal(eq(workSpec1), anyInt());
+        verify(mSystemJobScheduler, times(1))
+                .scheduleInternal(eq(workSpec2), anyInt());
     }
 
     @Test
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
index abf3450..2bb1271 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
@@ -30,6 +30,7 @@
 import android.arch.core.executor.ArchTaskExecutor;
 import android.arch.core.executor.TaskExecutor;
 import android.content.Context;
+import android.net.Network;
 import android.net.Uri;
 import android.os.Build;
 import android.os.PersistableBundle;
@@ -232,6 +233,27 @@
         assertThat(ContentUriTriggerLoggingWorker.sTriggeredContentUris, is(testContentUris));
     }
 
+    @Test
+    @LargeTest
+    @SdkSuppress(minSdkVersion = 28)
+    public void testStartJob_passesNetwork() throws InterruptedException {
+        WorkRequest work = new OneTimeWorkRequest.Builder(NetworkLoggingWorker.class).build();
+        insertWork(work);
+
+        Network mockNetwork = mock(Network.class);
+
+        JobParameters mockParams = createMockJobParameters(work.getStringId());
+        when(mockParams.getNetwork()).thenReturn(mockNetwork);
+
+        assertThat(NetworkLoggingWorker.sTimesUpdated, is(0));
+        assertThat(mSystemJobServiceSpy.onStartJob(mockParams), is(true));
+
+        Thread.sleep(1000L);
+
+        assertThat(NetworkLoggingWorker.sTimesUpdated, is(1));
+        assertThat(NetworkLoggingWorker.sNetwork, is(mockNetwork));
+    }
+
     private JobParameters createMockJobParameters(String id) {
         JobParameters jobParameters = mock(JobParameters.class);
 
@@ -253,13 +275,28 @@
         static Uri[] sTriggeredContentUris;
 
         @Override
-        public @NonNull WorkerResult doWork() {
+        public @NonNull Result doWork() {
             synchronized (ContentUriTriggerLoggingWorker.class) {
                 ++sTimesUpdated;
                 sTriggeredContentAuthorities = getTriggeredContentAuthorities();
                 sTriggeredContentUris = getTriggeredContentUris();
             }
-            return WorkerResult.SUCCESS;
+            return Result.SUCCESS;
+        }
+    }
+
+    public static class NetworkLoggingWorker extends Worker {
+
+        static int sTimesUpdated = 0;
+        static Network sNetwork;
+
+        @Override
+        public @NonNull Result doWork() {
+            synchronized (NetworkLoggingWorker.class) {
+                ++sTimesUpdated;
+                sNetwork = getNetwork();
+            }
+            return Result.SUCCESS;
         }
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index 54487e1..a86df66 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -144,7 +144,7 @@
                         mContext,
                         ConstraintTrackingWorker.class.getName(),
                         work.getId(),
-                        new Extras(input, Collections.<String>emptyList(), null));
+                        new Extras(input, Collections.<String>emptyList(), null, 1));
 
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
@@ -189,7 +189,7 @@
                         mContext,
                         ConstraintTrackingWorker.class.getName(),
                         work.getId(),
-                        new Extras(input, Collections.<String>emptyList(), null));
+                        new Extras(input, Collections.<String>emptyList(), null, 1));
 
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
@@ -233,7 +233,7 @@
                         mContext,
                         ConstraintTrackingWorker.class.getName(),
                         work.getId(),
-                        new Extras(input, Collections.<String>emptyList(), null));
+                        new Extras(input, Collections.<String>emptyList(), null, 1));
 
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
@@ -285,7 +285,7 @@
                         mContext,
                         ConstraintTrackingWorker.class.getName(),
                         work.getId(),
-                        new Extras(input, Collections.<String>emptyList(), null));
+                        new Extras(input, Collections.<String>emptyList(), null, 1));
 
         ConstraintTrackingWorker spyWorker = spy(worker);
         when(spyWorker.getWorkDatabase()).thenReturn(mDatabase);
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java
index b7dfbd6..d402e31 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/ChainedArgumentWorker.java
@@ -31,9 +31,9 @@
     public static final String ARGUMENT_VALUE = "value";
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         setOutputData(getChainedArguments());
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 
     public static Data getChainedArguments() {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java
index 67ff9ce..3be245f 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/EchoingWorker.java
@@ -23,8 +23,8 @@
 public class EchoingWorker extends Worker {
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         setOutputData(getInputData());
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java
index 49b6b4e..5fc95f7 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/ExceptionWorker.java
@@ -22,9 +22,8 @@
 
 public class ExceptionWorker extends Worker {
 
-    @NonNull
     @Override
-    public WorkerResult doWork() {
+    public @NonNull Result doWork() {
         throw new IllegalStateException();
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java
index 9be21bf..bb54de3 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/FailureWorker.java
@@ -27,8 +27,8 @@
 
 public class FailureWorker extends Worker {
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d("FailureWorker", "Returning FAILURE");
-        return WorkerResult.FAILURE;
+        return Result.FAILURE;
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java
index a16beaf..6258956 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/InfiniteTestWorker.java
@@ -27,7 +27,7 @@
 public class InfiniteTestWorker extends Worker {
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         while (true) {
         }
     }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java
index 30a6414..9c7d9f7 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/InterruptionAwareWorker.java
@@ -22,9 +22,8 @@
 
 public class InterruptionAwareWorker extends Worker {
 
-    @NonNull
     @Override
-    public WorkerResult doWork() {
+    public @NonNull Result doWork() {
         try {
             do {
                 Thread.sleep(1000L);
@@ -32,6 +31,6 @@
         } catch (InterruptedException e) {
             // Do nothing.
         }
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/LongRunningWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/LongRunningWorker.java
index f32b39c..d10a2e3 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/LongRunningWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/LongRunningWorker.java
@@ -23,12 +23,12 @@
 public class LongRunningWorker extends Worker {
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         try {
             Thread.sleep(5000L);
         } catch (InterruptedException e) {
             // Do nothing.
         }
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java
index 496dea6..a30dd5a 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/RetryWorker.java
@@ -27,8 +27,8 @@
 
 public class RetryWorker extends Worker {
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d("RetryWorker", "Returning RETRY");
-        return WorkerResult.RETRY;
+        return Result.RETRY;
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java
index dacf54b..f0faf86 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/SleepTestWorker.java
@@ -29,13 +29,13 @@
     public static final long SLEEP_DURATION = 5000;
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         try {
             Log.d("SleepTestWorker", "Sleeping : " + SLEEP_DURATION);
             Thread.sleep(SLEEP_DURATION);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java b/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java
index 5e6bd03..da6baff 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/worker/TestWorker.java
@@ -28,8 +28,8 @@
 public class TestWorker extends Worker {
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         Log.d("TestWorker", "TestWorker Ran!");
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
diff --git a/work/workmanager/src/main/AndroidManifest.xml b/work/workmanager/src/main/AndroidManifest.xml
index e060cf0..5fa9cd9 100644
--- a/work/workmanager/src/main/AndroidManifest.xml
+++ b/work/workmanager/src/main/AndroidManifest.xml
@@ -73,7 +73,7 @@
         </receiver>
         <receiver
             android:name="androidx.work.impl.background.systemalarm.RescheduleReceiver"
-            android:enabled="@bool/enable_system_alarm_service_default">
+            android:enabled="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED" />
                 <action android:name="android.intent.action.TIME_SET" />
diff --git a/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java b/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
index 0ebe01d..fe1a387 100644
--- a/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/PeriodicWorkRequest.java
@@ -29,13 +29,11 @@
 public final class PeriodicWorkRequest extends WorkRequest {
 
     /**
-     * The minimum interval duration for {@link PeriodicWorkRequest}, in milliseconds.
-     * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/job/JobInfo.java#110}.
+     * The minimum interval duration for {@link PeriodicWorkRequest} (in milliseconds).
      */
     public static final long MIN_PERIODIC_INTERVAL_MILLIS = 15 * 60 * 1000L; // 15 minutes.
     /**
-     * The minimum flex duration for {@link PeriodicWorkRequest}, in milliseconds.
-     * Based on {@see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/job/JobInfo.java#113}.
+     * The minimum flex duration for {@link PeriodicWorkRequest} (in milliseconds).
      */
     public static final long MIN_PERIODIC_FLEX_MILLIS = 5 * 60 * 1000L; // 5 minutes.
 
diff --git a/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java b/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java
index 2d7cd32..0b27021 100644
--- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java
+++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkContinuation.java
@@ -18,6 +18,8 @@
 
 import android.support.annotation.WorkerThread;
 
+import java.util.List;
+
 /**
  * Blocking methods for {@link WorkContinuation} operations.  These methods are expected to be
  * called from a background thread.
@@ -31,4 +33,13 @@
      */
     @WorkerThread
     void enqueueSync();
+
+    /**
+     * Returns a {@link List} of {@link WorkStatus} that provides information about work,
+     * their progress, and any resulting output in the {@link WorkContinuation}.
+     *
+     * @return A {@link  List} of {@link WorkStatus}es
+     */
+    @WorkerThread
+    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 d595784..3359905 100644
--- a/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/SynchronousWorkManager.java
@@ -109,6 +109,15 @@
     void cancelUniqueWorkSync(@NonNull String uniqueWorkName);
 
     /**
+     * Cancels all unfinished work in a synchronous fashion.  <b>Use this method with extreme
+     * caution!</b>  By invoking it, you will potentially affect other modules or libraries in your
+     * codebase.  It is strongly recommended that you use one of the other cancellation methods at
+     * your disposal.
+     */
+    @WorkerThread
+    void cancelAllWorkSync();
+
+    /**
      * Gets the {@link WorkStatus} of a given work id in a synchronous fashion.  This method is
      * expected to be called from a background thread.
      *
diff --git a/work/workmanager/src/main/java/androidx/work/WorkManager.java b/work/workmanager/src/main/java/androidx/work/WorkManager.java
index 41ddd25..00a41d3 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkManager.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkManager.java
@@ -281,6 +281,13 @@
     public abstract void cancelUniqueWork(@NonNull String uniqueWorkName);
 
     /**
+     * Cancels all unfinished work.  <b>Use this method with extreme caution!</b>  By invoking it,
+     * you will potentially affect other modules or libraries in your codebase.  It is strongly
+     * recommended that you use one of the other cancellation methods at your disposal.
+     */
+    public abstract void cancelAllWork();
+
+    /**
      * Gets a {@link LiveData} of the {@link WorkStatus} for a given work id.
      *
      * @param id The id of the work
diff --git a/work/workmanager/src/main/java/androidx/work/WorkRequest.java b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
index 5636bf5..2232444 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkRequest.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkRequest.java
@@ -35,17 +35,17 @@
 public abstract class WorkRequest {
 
     /**
-     * {@see https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/app/job/JobInfo.java#77}
+     * The default initial backoff time (in milliseconds) for work that has to be retried.
      */
     public static final long DEFAULT_BACKOFF_DELAY_MILLIS = 30000L;
 
     /**
-     * {@see https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/app/job/JobInfo.java#82}
+     * The maximum backoff time (in milliseconds) for work that has to be retried.
      */
     public static final long MAX_BACKOFF_MILLIS = 5 * 60 * 60 * 1000; // 5 hours.
 
     /**
-     * {@see https://android.googlesource.com/platform/frameworks/base/+/oreo-release/core/java/android/app/job/JobInfo.java#119}
+     * The minimum backoff time for work (in milliseconds) that has to be retried.
      */
     public static final long MIN_BACKOFF_MILLIS = 10 * 1000; // 10 seconds.
 
diff --git a/work/workmanager/src/main/java/androidx/work/Worker.java b/work/workmanager/src/main/java/androidx/work/Worker.java
index 97bfbaa..58a5c22 100644
--- a/work/workmanager/src/main/java/androidx/work/Worker.java
+++ b/work/workmanager/src/main/java/androidx/work/Worker.java
@@ -17,6 +17,7 @@
 package androidx.work;
 
 import android.content.Context;
+import android.net.Network;
 import android.net.Uri;
 import android.support.annotation.Keep;
 import android.support.annotation.NonNull;
@@ -42,7 +43,7 @@
     /**
      * The result of the Worker's computation that is returned in the {@link #doWork()} method.
      */
-    public enum WorkerResult {
+    public enum Result {
         /**
          * Used to indicate that the work completed successfully.  Any work that depends on this
          * can be executed as long as all of its other dependencies and constraints are met.
@@ -136,14 +137,35 @@
     }
 
     /**
+     * Gets the {@link Network} to use for this Worker.  This method returns {@code null} if there
+     * is no network needed for this work request.
+     *
+     * @return The {@link Network} specified by the OS to be used with this Worker
+     */
+    @RequiresApi(28)
+    public final @Nullable Network getNetwork() {
+        Extras.RuntimeExtras runtimeExtras = mExtras.getRuntimeExtras();
+        return (runtimeExtras == null) ? null : runtimeExtras.network;
+    }
+
+    /**
+     * Gets the current run attempt count for this work.
+     *
+     * @return The current run attempt count for this work.
+     */
+    public final int getRunAttemptCount() {
+        return mExtras.getRunAttemptCount();
+    }
+
+    /**
      * Override this method to do your actual background processing.
      *
-     * @return The result of the work, corresponding to a {@link WorkerResult} value.  If a
+     * @return The result of the work, corresponding to a {@link Result} value.  If a
      * different value is returned, the result shall be defaulted to
-     * {@link Worker.WorkerResult#FAILURE}.
+     * {@link Result#FAILURE}.
      */
     @WorkerThread
-    public abstract @NonNull WorkerResult doWork();
+    public abstract @NonNull Result doWork();
 
     /**
      * Call this method to pass an {@link Data} object to {@link Worker} that is
@@ -154,7 +176,7 @@
      * unique.  New values and types will clobber old values and types, and if there are multiple
      * parent Workers of a child Worker, the order of clobbering may not be deterministic.
      *
-     * This method is invoked after {@link #doWork()} returns {@link Worker.WorkerResult#SUCCESS}
+     * This method is invoked after {@link #doWork()} returns {@link Result#SUCCESS}
      * and there are chained jobs available.
      *
      * For example, if you had this structure:
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Extras.java b/work/workmanager/src/main/java/androidx/work/impl/Extras.java
index 0828672..bbc48a1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Extras.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Extras.java
@@ -16,9 +16,11 @@
 
 package androidx.work.impl;
 
+import android.net.Network;
 import android.net.Uri;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.annotation.RestrictTo;
 
 import androidx.work.Data;
@@ -38,13 +40,16 @@
     private @NonNull Data mInputData;
     private @NonNull Set<String> mTags;
     private @Nullable RuntimeExtras mRuntimeExtras;
+    private int mRunAttemptCount;
 
     public Extras(@NonNull Data inputData,
             @NonNull List<String> tags,
-            @Nullable RuntimeExtras runtimeExtras) {
+            @Nullable RuntimeExtras runtimeExtras,
+            int runAttemptCount) {
         mInputData = inputData;
         mTags = new HashSet<>(tags);
         mRuntimeExtras = runtimeExtras;
+        mRunAttemptCount = runAttemptCount;
     }
 
     public @NonNull Data getInputData() {
@@ -59,6 +64,10 @@
         return mRuntimeExtras;
     }
 
+    public int getRunAttemptCount() {
+        return mRunAttemptCount;
+    }
+
     /**
      * Extra runtime information for Workers.
      *
@@ -69,5 +78,8 @@
 
         public String[] triggeredContentAuthorities;
         public Uri[] triggeredContentUris;
+
+        @RequiresApi(28)
+        public Network network;
     }
 }
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 891aefa..c6e5b81 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -25,7 +25,6 @@
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
-import androidx.work.Configuration;
 import androidx.work.impl.background.systemalarm.SystemAlarmScheduler;
 import androidx.work.impl.background.systemalarm.SystemAlarmService;
 import androidx.work.impl.background.systemjob.SystemJobScheduler;
@@ -102,14 +101,14 @@
 
     static @NonNull Scheduler createBestAvailableBackgroundScheduler(
             @NonNull Context context,
-            @NonNull Configuration configuration) {
+            @NonNull WorkManagerImpl workManager) {
 
         Scheduler scheduler;
         boolean enableFirebaseJobService = false;
         boolean enableSystemAlarmService = false;
 
         if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
-            scheduler = new SystemJobScheduler(context, configuration);
+            scheduler = new SystemJobScheduler(context, workManager);
             setComponentEnabled(context, SystemJobService.class, true);
             Log.d(TAG, "Created SystemJobScheduler and enabled SystemJobService");
         } else {
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 9361218..3713dca 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkContinuationImpl.java
@@ -164,6 +164,14 @@
     }
 
     @Override
+    public List<WorkStatus> getStatusesSync() {
+        if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
+            throw new IllegalStateException("Cannot getStatusesSync on main thread!");
+        }
+        return mWorkManagerImpl.getStatusesByIdSync(mAllIds);
+    }
+
+    @Override
     public void enqueue() {
         // Only enqueue if not already enqueued.
         if (!mEnqueued) {
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 f5fb992..e5d5f9d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -30,10 +30,10 @@
 import android.support.annotation.RestrictTo;
 
 import androidx.work.Data;
-import androidx.work.impl.model.AlarmInfo;
-import androidx.work.impl.model.AlarmInfoDao;
 import androidx.work.impl.model.Dependency;
 import androidx.work.impl.model.DependencyDao;
+import androidx.work.impl.model.SystemIdInfo;
+import androidx.work.impl.model.SystemIdInfoDao;
 import androidx.work.impl.model.WorkName;
 import androidx.work.impl.model.WorkNameDao;
 import androidx.work.impl.model.WorkSpec;
@@ -50,15 +50,13 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-// TODO (rahulrav@) Figure out if / how we export the Room Schema
 @Database(entities = {
         Dependency.class,
         WorkSpec.class,
         WorkTag.class,
-        AlarmInfo.class,
+        SystemIdInfo.class,
         WorkName.class},
-        version = 1,
-        exportSchema = false)
+        version = 2)
 @TypeConverters(value = {Data.class, WorkTypeConverters.class})
 public abstract class WorkDatabase extends RoomDatabase {
 
@@ -97,7 +95,10 @@
         } else {
             builder = Room.databaseBuilder(context, WorkDatabase.class, DB_NAME);
         }
-        return builder.addCallback(generateCleanupCallback()).build();
+        return builder.addCallback(generateCleanupCallback())
+                .addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
+                .addMigrations(WorkDatabaseMigrations.MIGRATION_2_1)
+                .build();
     }
 
     static Callback generateCleanupCallback() {
@@ -145,9 +146,9 @@
     public abstract WorkTagDao workTagDao();
 
     /**
-     * @return The Data Access Object for {@link AlarmInfo}s.
+     * @return The Data Access Object for {@link SystemIdInfo}s.
      */
-    public abstract AlarmInfoDao alarmInfoDao();
+    public abstract SystemIdInfoDao systemIdInfoDao();
 
     /**
      * @return The Data Access Object for {@link WorkName}s.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
new file mode 100644
index 0000000..0c3e3db
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.impl;
+
+import android.arch.persistence.db.SupportSQLiteDatabase;
+import android.arch.persistence.room.migration.Migration;
+import android.support.annotation.NonNull;
+import android.support.annotation.RestrictTo;
+
+/**
+ * Migration helpers for {@link androidx.work.impl.WorkDatabase}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class WorkDatabaseMigrations {
+
+    private WorkDatabaseMigrations() {
+        // does nothing
+    }
+
+    // Known WorkDatabase versions
+    private static final int VERSION_1 = 1;
+    private static final int VERSION_2 = 2;
+
+    private static final String CREATE_SYSTEM_ID_INFO =
+            "CREATE TABLE IF NOT EXISTS `systemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`"
+                    + " 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 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
+     * {@code systemIdInfo} table.
+     */
+    public static Migration MIGRATION_1_2 = new Migration(VERSION_1, VERSION_2) {
+        @Override
+        public void migrate(@NonNull SupportSQLiteDatabase database) {
+            database.execSQL(CREATE_SYSTEM_ID_INFO);
+            database.execSQL(MIGRATE_ALARM_INFO_TO_SYSTEM_ID_INFO);
+            database.execSQL(REMOVE_ALARM_INFO);
+        }
+    };
+
+    /**
+     * Removes the {@code alarmInfo} table and substitutes it for a more general
+     * {@code systemIdInfo} table.
+     */
+    public static Migration MIGRATION_2_1 = new Migration(VERSION_2, VERSION_1) {
+        @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);
+        }
+    };
+}
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 e91df1f..3d61cae 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -182,6 +182,16 @@
     }
 
     /**
+     * @return The {@link Configuration} instance associated with this WorkManager.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    public Configuration getConfiguration() {
+        return mConfiguration;
+    }
+
+    /**
      * @return The {@link Scheduler}s associated with this WorkManager based on the device's
      * capabilities, SDK version, etc.
      * @hide
@@ -191,7 +201,7 @@
         // Initialized at construction time. So no need to synchronize.
         if (mSchedulers == null) {
             mSchedulers = Arrays.asList(
-                    Schedulers.createBestAvailableBackgroundScheduler(mContext, mConfiguration),
+                    Schedulers.createBestAvailableBackgroundScheduler(mContext, this),
                     new GreedyScheduler(mContext, this));
         }
         return mSchedulers;
@@ -324,6 +334,17 @@
     }
 
     @Override
+    public void cancelAllWork() {
+        mTaskExecutor.executeOnBackgroundThread(CancelWorkRunnable.forAll(this));
+    }
+
+    @Override
+    public void cancelAllWorkSync() {
+        assertBackgroundThread("Cannot cancelAllWorkSync on main thread!");
+        CancelWorkRunnable.forAll(this).run();
+    }
+
+    @Override
     public LiveData<WorkStatus> getStatusById(@NonNull UUID id) {
         WorkSpecDao dao = mWorkDatabase.workSpecDao();
         LiveData<List<WorkSpec.WorkStatusPojo>> inputLiveData =
@@ -398,6 +419,13 @@
         return LiveDataUtils.dedupedMappedLiveDataFor(inputLiveData, WorkSpec.WORK_STATUS_MAPPER);
     }
 
+    List<WorkStatus> getStatusesByIdSync(@NonNull List<String> workSpecIds) {
+        List<WorkSpec.WorkStatusPojo> workStatusPojos = mWorkDatabase.workSpecDao()
+                .getWorkStatusPojoForIds(workSpecIds);
+
+        return WorkSpec.WORK_STATUS_MAPPER.apply(workStatusPojos);
+    }
+
     /**
      * @param workSpecId The {@link WorkSpec} id to start
      * @hide
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 57e968a..b6d4fc1 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -124,8 +124,11 @@
             input = inputMerger.merge(inputs);
         }
 
-        Extras extras =
-                new Extras(input, mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId), mRuntimeExtras);
+        Extras extras = new Extras(
+                input,
+                mWorkTagDao.getTagsForWorkSpecId(mWorkSpecId),
+                mRuntimeExtras,
+                mWorkSpec.runAttemptCount);
 
         // Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override
         // in test mode.
@@ -146,11 +149,11 @@
                 return;
             }
 
-            Worker.WorkerResult result;
+            Worker.Result result;
             try {
                 result = mWorker.doWork();
             } catch (Exception | Error e) {
-                result = Worker.WorkerResult.FAILURE;
+                result = Worker.Result.FAILURE;
             }
 
             try {
@@ -231,7 +234,7 @@
         });
     }
 
-    private void handleResult(Worker.WorkerResult result) {
+    private void handleResult(Worker.Result result) {
         switch (result) {
             case SUCCESS: {
                 Log.d(TAG, String.format("Worker result SUCCESS for %s", mWorkSpecId));
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
index e545e54..e46d95a 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
@@ -29,8 +29,8 @@
 
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.model.AlarmInfo;
-import androidx.work.impl.model.AlarmInfoDao;
+import androidx.work.impl.model.SystemIdInfo;
+import androidx.work.impl.model.SystemIdInfoDao;
 import androidx.work.impl.utils.IdGenerator;
 
 /**
@@ -58,22 +58,22 @@
             long triggerAtMillis) {
 
         WorkDatabase workDatabase = workManager.getWorkDatabase();
-        AlarmInfoDao alarmInfoDao = workDatabase.alarmInfoDao();
-        AlarmInfo alarmInfo = alarmInfoDao.getAlarmInfo(workSpecId);
-        if (alarmInfo != null) {
-            cancelExactAlarm(context, workSpecId, alarmInfo.alarmId);
-            setExactAlarm(context, workSpecId, alarmInfo.alarmId, triggerAtMillis);
+        SystemIdInfoDao systemIdInfoDao = workDatabase.systemIdInfoDao();
+        SystemIdInfo systemIdInfo = systemIdInfoDao.getSystemIdInfo(workSpecId);
+        if (systemIdInfo != null) {
+            cancelExactAlarm(context, workSpecId, systemIdInfo.systemId);
+            setExactAlarm(context, workSpecId, systemIdInfo.systemId, triggerAtMillis);
         } else {
             IdGenerator idGenerator = new IdGenerator(context);
             int alarmId = idGenerator.nextAlarmManagerId();
-            AlarmInfo newAlarmInfo = new AlarmInfo(workSpecId, alarmId);
-            alarmInfoDao.insertAlarmInfo(newAlarmInfo);
+            SystemIdInfo newSystemIdInfo = new SystemIdInfo(workSpecId, alarmId);
+            systemIdInfoDao.insertSystemIdInfo(newSystemIdInfo);
             setExactAlarm(context, workSpecId, alarmId, triggerAtMillis);
         }
     }
 
     /**
-     * Cancels an existing alarm and removes the {@link AlarmInfo}.
+     * Cancels an existing alarm and removes the {@link SystemIdInfo}.
      *
      * @param context     The application {@link Context}.
      * @param workManager The instance of {@link WorkManagerImpl}.
@@ -85,12 +85,12 @@
             @NonNull String workSpecId) {
 
         WorkDatabase workDatabase = workManager.getWorkDatabase();
-        AlarmInfoDao alarmInfoDao = workDatabase.alarmInfoDao();
-        AlarmInfo alarmInfo = alarmInfoDao.getAlarmInfo(workSpecId);
-        if (alarmInfo != null) {
-            cancelExactAlarm(context, workSpecId, alarmInfo.alarmId);
-            Log.d(TAG, String.format("Removing AlarmInfo for workSpecId (%s)", workSpecId));
-            alarmInfoDao.removeAlarmInfo(workSpecId);
+        SystemIdInfoDao systemIdInfoDao = workDatabase.systemIdInfoDao();
+        SystemIdInfo systemIdInfo = systemIdInfoDao.getSystemIdInfo(workSpecId);
+        if (systemIdInfo != null) {
+            cancelExactAlarm(context, workSpecId, systemIdInfo.systemId);
+            Log.d(TAG, String.format("Removing SystemIdInfo for workSpecId (%s)", workSpecId));
+            systemIdInfoDao.removeSystemIdInfo(workSpecId);
         }
     }
 
@@ -105,7 +105,7 @@
                 context, alarmId, delayMet, PendingIntent.FLAG_NO_CREATE);
         if (pendingIntent != null && alarmManager != null) {
             Log.d(TAG, String.format(
-                    "Cancelling existing alarm with (workSpecId, alarmId) (%s, %s)",
+                    "Cancelling existing alarm with (workSpecId, systemId) (%s, %s)",
                     workSpecId,
                     alarmId));
             alarmManager.cancel(pendingIntent);
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 27eb6af..a331479 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
@@ -19,14 +19,46 @@
 import android.content.BroadcastReceiver;
 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.
  */
 public class RescheduleReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "RescheduleReceiver";
+
     @Override
     public void onReceive(Context context, Intent intent) {
-        Intent reschedule = CommandHandler.createRescheduleIntent(context);
-        context.startService(reschedule);
+        if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+            if (WorkManager.getInstance() == 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));
+            }
+        } else {
+            Intent reschedule = CommandHandler.createRescheduleIntent(context);
+            context.startService(reschedule);
+        }
     }
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
index 41a94b3..5915d66 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
@@ -29,13 +29,11 @@
 import android.util.Log;
 
 import androidx.work.BackoffPolicy;
-import androidx.work.Configuration;
 import androidx.work.Constraints;
 import androidx.work.ContentUriTriggers;
 import androidx.work.NetworkType;
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.WorkSpec;
-import androidx.work.impl.utils.IdGenerator;
 
 /**
  * Converts a {@link WorkSpec} into a JobInfo.
@@ -48,28 +46,11 @@
     static final String EXTRA_IS_PERIODIC = "EXTRA_IS_PERIODIC";
 
     private final ComponentName mWorkServiceComponent;
-    private final Configuration mConfiguration;
-    private final IdGenerator mIdGenerator;
-
-    /**
-     * Constructs a {@link IdGenerator}.
-     *
-     * @param context A non-null {@link Context}.
-     */
-    SystemJobInfoConverter(@NonNull Context context, @NonNull Configuration configuration) {
-        this(context, configuration, new IdGenerator(context));
-    }
 
     @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
-    SystemJobInfoConverter(
-            @NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull IdGenerator idGenerator) {
-
+    SystemJobInfoConverter(@NonNull Context context) {
         Context appContext = context.getApplicationContext();
         mWorkServiceComponent = new ComponentName(appContext, SystemJobService.class);
-        mConfiguration = configuration;
-        mIdGenerator = idGenerator;
     }
 
     /**
@@ -78,13 +59,11 @@
      * Note: All {@link JobInfo} are set to persist on reboot.
      *
      * @param workSpec The {@link WorkSpec} to convert
+     * @param jobId The {@code jobId} to use. This is useful when de-duping jobs on reschedule.
      * @return The {@link JobInfo} representing the same information as the {@link WorkSpec}
      */
-    JobInfo convert(WorkSpec workSpec) {
+    JobInfo convert(WorkSpec workSpec, int jobId) {
         Constraints constraints = workSpec.constraints;
-        int jobId = mIdGenerator.nextJobSchedulerIdWithRange(
-                mConfiguration.getMinJobSchedulerID(),
-                mConfiguration.getMaxJobSchedulerID());
         // TODO(janclarin): Support newer required network types if unsupported by API version.
         int jobInfoNetworkType = convertNetworkType(constraints.getRequiredNetworkType());
         PersistableBundle extras = new PersistableBundle();
@@ -122,12 +101,11 @@
             for (ContentUriTriggers.Trigger trigger : constraints.getContentUriTriggers()) {
                 builder.addTriggerContentUri(convertContentUriTrigger(trigger));
             }
-        } else {
-            // Jobs with Content Uri Triggers cannot be persisted
-            builder.setPersisted(true);
         }
 
-        // TODO(janclarin): Support requires[Battery|Storage]NotLow for versions older than 26.
+        // We don't want to persist these jobs because we reschedule these jobs on BOOT_COMPLETED.
+        // That way ForceStopRunnable correctly reschedules Jobs when necessary.
+        builder.setPersisted(false);
         if (Build.VERSION.SDK_INT >= 26) {
             builder.setRequiresBatteryNotLow(constraints.requiresBatteryNotLow());
             builder.setRequiresStorageNotLow(constraints.requiresStorageNotLow());
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index 0ed4935..98aff4e 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -25,10 +25,12 @@
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
-import androidx.work.Configuration;
 import androidx.work.impl.Scheduler;
+import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.SystemIdInfo;
 import androidx.work.impl.model.WorkSpec;
+import androidx.work.impl.utils.IdGenerator;
 
 import java.util.List;
 
@@ -44,32 +46,69 @@
     private static final String TAG = "SystemJobScheduler";
 
     private final JobScheduler mJobScheduler;
+    private final WorkManagerImpl mWorkManager;
+    private final IdGenerator mIdGenerator;
     private final SystemJobInfoConverter mSystemJobInfoConverter;
 
-    public SystemJobScheduler(@NonNull Context context, @NonNull Configuration configuration) {
-        this((JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE),
-                new SystemJobInfoConverter(context, configuration));
+    public SystemJobScheduler(@NonNull Context context, @NonNull WorkManagerImpl workManager) {
+        this(context,
+                workManager,
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE),
+                new SystemJobInfoConverter(context));
     }
 
     @VisibleForTesting
     public SystemJobScheduler(
+            Context context,
+            WorkManagerImpl workManager,
             JobScheduler jobScheduler,
             SystemJobInfoConverter systemJobInfoConverter) {
+        mWorkManager = workManager;
         mJobScheduler = jobScheduler;
+        mIdGenerator = new IdGenerator(context);
         mSystemJobInfoConverter = systemJobInfoConverter;
     }
 
     @Override
     public void schedule(WorkSpec... workSpecs) {
-        for (WorkSpec workSpec : workSpecs) {
-            scheduleInternal(workSpec);
+        WorkDatabase workDatabase = mWorkManager.getWorkDatabase();
 
-            // API 23 JobScheduler only kicked off jobs if there were at least two jobs in the
-            // queue, even if the job constraints were met.  This behavior was considered
-            // undesirable and later changed in Marshmallow MR1.  To match the new behavior, we will
-            // double-schedule jobs on API 23 and dedupe them in SystemJobService as needed.
-            if (Build.VERSION.SDK_INT == 23) {
-                scheduleInternal(workSpec);
+        for (WorkSpec workSpec : workSpecs) {
+            try {
+                workDatabase.beginTransaction();
+
+                SystemIdInfo info = workDatabase.systemIdInfoDao()
+                        .getSystemIdInfo(workSpec.id);
+
+                int jobId = info != null ? info.systemId : mIdGenerator.nextJobSchedulerIdWithRange(
+                        mWorkManager.getConfiguration().getMinJobSchedulerID(),
+                        mWorkManager.getConfiguration().getMaxJobSchedulerID());
+
+                if (info == null) {
+                    SystemIdInfo newSystemIdInfo = new SystemIdInfo(workSpec.id, jobId);
+                    mWorkManager.getWorkDatabase()
+                            .systemIdInfoDao()
+                            .insertSystemIdInfo(newSystemIdInfo);
+                }
+
+                scheduleInternal(workSpec, jobId);
+
+                // API 23 JobScheduler only kicked off jobs if there were at least two jobs in the
+                // queue, even if the job constraints were met.  This behavior was considered
+                // undesirable and later changed in Marshmallow MR1.  To match the new behavior,
+                // we will double-schedule jobs on API 23 and de-dupe them
+                // in SystemJobService as needed.
+                if (Build.VERSION.SDK_INT == 23) {
+                    int nextJobId = mIdGenerator.nextJobSchedulerIdWithRange(
+                            mWorkManager.getConfiguration().getMinJobSchedulerID(),
+                            mWorkManager.getConfiguration().getMaxJobSchedulerID());
+
+                    scheduleInternal(workSpec, nextJobId);
+                }
+
+                workDatabase.setTransactionSuccessful();
+            } finally {
+                workDatabase.endTransaction();
             }
         }
     }
@@ -80,9 +119,9 @@
      * @param workSpec The {@link WorkSpec} to schedule with JobScheduler.
      */
     @VisibleForTesting
-    public void scheduleInternal(WorkSpec workSpec) {
-        JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec);
-        Log.d(TAG, String.format("Scheduling work ID %s Job ID %s", workSpec.id, jobInfo.getId()));
+    public void scheduleInternal(WorkSpec workSpec, int jobId) {
+        JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec, jobId);
+        Log.d(TAG, String.format("Scheduling work ID %s Job ID %s", workSpec.id, jobId));
         mJobScheduler.schedule(jobInfo);
     }
 
@@ -96,6 +135,12 @@
             for (JobInfo jobInfo : allJobInfos) {
                 if (workSpecId.equals(
                         jobInfo.getExtras().getString(SystemJobInfoConverter.EXTRA_WORK_SPEC_ID))) {
+
+                    // Its safe to call this method twice.
+                    mWorkManager.getWorkDatabase()
+                            .systemIdInfoDao()
+                            .removeSystemIdInfo(workSpecId);
+
                     mJobScheduler.cancel(jobInfo.getId());
 
                     // See comment in #schedule.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index 4137074..373bc14 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -90,13 +90,17 @@
 
         Extras.RuntimeExtras runtimeExtras = null;
         if (Build.VERSION.SDK_INT >= 24) {
+            runtimeExtras = new Extras.RuntimeExtras();
             if (params.getTriggeredContentUris() != null
                     || params.getTriggeredContentAuthorities() != null) {
-                runtimeExtras = new Extras.RuntimeExtras();
                 runtimeExtras.triggeredContentUris = params.getTriggeredContentUris();
                 runtimeExtras.triggeredContentAuthorities =
                         params.getTriggeredContentAuthorities();
             }
+
+            if (Build.VERSION.SDK_INT >= 28) {
+                runtimeExtras.network = params.getNetwork();
+            }
         }
 
         mWorkManagerImpl.startWork(workSpecId, runtimeExtras);
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/AlarmInfoDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/AlarmInfoDao.java
deleted file mode 100644
index dc4d1ed..0000000
--- a/work/workmanager/src/main/java/androidx/work/impl/model/AlarmInfoDao.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.work.impl.model;
-
-import android.arch.persistence.room.Dao;
-import android.arch.persistence.room.Insert;
-import android.arch.persistence.room.OnConflictStrategy;
-import android.arch.persistence.room.Query;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-/**
- * A Data Access Object for {@link AlarmInfo}.
- */
-@Dao
-public interface AlarmInfoDao {
-
-    /**
-     * Inserts a {@link AlarmInfo} into the database.
-     *
-     * @param alarmInfo The {@link AlarmInfo} to be inserted into the database.
-     */
-    @Insert(onConflict = OnConflictStrategy.FAIL)
-    void insertAlarmInfo(@NonNull AlarmInfo alarmInfo);
-
-    /**
-     * @param workSpecId The {@link WorkSpec} identifier.
-     * @return The instance of {@link AlarmInfo} if exists.
-     */
-    @Nullable
-    @Query("SELECT * FROM alarmInfo WHERE work_spec_id=:workSpecId")
-    AlarmInfo getAlarmInfo(@NonNull String workSpecId);
-
-    /**
-     * Removes alarms corresponding to the {@link WorkSpec} identifier.
-     *
-     * @param workSpecId The {@link WorkSpec} identifier.
-     */
-    @Query("DELETE FROM alarmInfo where work_spec_id=:workSpecId")
-    void removeAlarmInfo(@NonNull String workSpecId);
-
-}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/AlarmInfo.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
similarity index 78%
rename from work/workmanager/src/main/java/androidx/work/impl/model/AlarmInfo.java
rename to work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
index 3529126..e869762 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/AlarmInfo.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfo.java
@@ -24,11 +24,11 @@
 import android.support.annotation.RestrictTo;
 
 /**
- * Stores Alarm ids for a {@link WorkSpec}.
+ * Stores system ids for a {@link WorkSpec} id.
  *
  * @hide
  */
-@Entity(tableName = "alarmInfo",
+@Entity(tableName = "systemIdInfo",
         foreignKeys = {
                 @ForeignKey(
                         entity = WorkSpec.class,
@@ -37,19 +37,18 @@
                         onDelete = ForeignKey.CASCADE,
                         onUpdate = ForeignKey.CASCADE)})
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class AlarmInfo {
-
+public class SystemIdInfo {
     @NonNull
     @PrimaryKey
     @ColumnInfo(name = "work_spec_id")
     public final String workSpecId;
 
-    @ColumnInfo(name = "alarm_id")
-    public final int alarmId;
+    @ColumnInfo(name = "system_id")
+    public final int systemId;
 
-    public AlarmInfo(@NonNull String workSpecId, int alarmId) {
+    public SystemIdInfo(@NonNull String workSpecId, int systemId) {
         this.workSpecId = workSpecId;
-        this.alarmId = alarmId;
+        this.systemId = systemId;
     }
 
     @Override
@@ -57,16 +56,16 @@
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
-        AlarmInfo alarmInfo = (AlarmInfo) o;
+        SystemIdInfo that = (SystemIdInfo) o;
 
-        if (alarmId != alarmInfo.alarmId) return false;
-        return workSpecId.equals(alarmInfo.workSpecId);
+        if (systemId != that.systemId) return false;
+        return workSpecId.equals(that.workSpecId);
     }
 
     @Override
     public int hashCode() {
         int result = workSpecId.hashCode();
-        result = 31 * result + alarmId;
+        result = 31 * result + systemId;
         return result;
     }
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
new file mode 100644
index 0000000..bcee05b
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/SystemIdInfoDao.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.work.impl.model;
+
+import static android.arch.persistence.room.OnConflictStrategy.REPLACE;
+
+import android.arch.persistence.room.Dao;
+import android.arch.persistence.room.Insert;
+import android.arch.persistence.room.Query;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+/**
+ * A Data Access Object for {@link SystemIdInfo}.
+ */
+@Dao
+public interface SystemIdInfoDao {
+    /**
+     * Inserts a {@link SystemIdInfo} into the database.
+     *
+     * @param systemIdInfo The {@link SystemIdInfo} to be inserted into the database.
+     */
+    @Insert(onConflict = REPLACE)
+    void insertSystemIdInfo(@NonNull SystemIdInfo systemIdInfo);
+
+    /**
+     * @param workSpecId The {@link WorkSpec} identifier.
+     * @return The instance of {@link SystemIdInfo} if exists.
+     */
+    @Nullable
+    @Query("SELECT * FROM systemIdInfo WHERE work_spec_id=:workSpecId")
+    SystemIdInfo getSystemIdInfo(@NonNull String workSpecId);
+
+    /**
+     * Removes {@link SystemIdInfo} corresponding to the {@link WorkSpec} identifier.
+     *
+     * @param workSpecId The {@link WorkSpec} identifier.
+     */
+    @Query("DELETE FROM systemIdInfo where work_spec_id=:workSpecId")
+    void removeSystemIdInfo(@NonNull String workSpecId);
+}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java
index 756cdbb..f6ed812 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkNameDao.java
@@ -16,7 +16,7 @@
 
 package androidx.work.impl.model;
 
-import static android.arch.persistence.room.OnConflictStrategy.FAIL;
+import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
 
 import android.arch.persistence.room.Dao;
 import android.arch.persistence.room.Insert;
@@ -35,7 +35,7 @@
      *
      * @param workName The {@link WorkName} to insert
      */
-    @Insert(onConflict = FAIL)
+    @Insert(onConflict = IGNORE)
     void insert(WorkName workName);
 
     /**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
index e3b15da..7d9775d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkSpecDao.java
@@ -153,6 +153,17 @@
     WorkSpec.WorkStatusPojo getWorkStatusPojoForId(String id);
 
     /**
+     * For a list of {@link WorkSpec} identifiers, retrieves a {@link List} of their
+     * {@link WorkSpec.WorkStatusPojo}.
+     *
+     * @param ids The identifier of the {@link WorkSpec}s
+     * @return A {@link List} of {@link WorkSpec.WorkStatusPojo}
+     */
+    @Transaction
+    @Query("SELECT id, state, output FROM workspec WHERE id IN (:ids)")
+    List<WorkSpec.WorkStatusPojo> getWorkStatusPojoForIds(List<String> ids);
+
+    /**
      * For a list of {@link WorkSpec} identifiers, retrieves a {@link LiveData} list of their
      * {@link WorkSpec.WorkStatusPojo}.
      *
@@ -241,6 +252,14 @@
     List<String> getUnfinishedWorkWithName(@NonNull String name);
 
     /**
+     * Retrieves work ids for all unfinished work.
+     *
+     * @return A list of work ids
+     */
+    @Query("SELECT id FROM workspec WHERE state NOT IN " + COMPLETED_STATES)
+    List<String> getAllUnfinishedWork();
+
+    /**
      * Marks a {@link WorkSpec} as scheduled.
      *
      * @param id        The identifier for the {@link WorkSpec}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java
index 0f85f52..275c9de 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/model/WorkTagDao.java
@@ -16,7 +16,7 @@
 
 package androidx.work.impl.model;
 
-import static android.arch.persistence.room.OnConflictStrategy.FAIL;
+import static android.arch.persistence.room.OnConflictStrategy.IGNORE;
 
 import android.arch.persistence.room.Dao;
 import android.arch.persistence.room.Insert;
@@ -35,7 +35,7 @@
      *
      * @param workTag The {@link WorkTag} to insert
      */
-    @Insert(onConflict = FAIL)
+    @Insert(onConflict = IGNORE)
     void insert(WorkTag workTag);
 
     /**
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
index fdaf1d4..46f1b45 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/CancelWorkRunnable.java
@@ -156,4 +156,31 @@
             }
         };
     }
+
+    /**
+     * Creates a {@link CancelWorkRunnable} that cancels all work.
+     *
+     * @param workManagerImpl The {@link WorkManagerImpl} to use
+     * @return A {@link Runnable} that cancels all work
+     */
+    public static Runnable forAll(@NonNull final WorkManagerImpl workManagerImpl) {
+        return new CancelWorkRunnable() {
+            @Override
+            public void run() {
+                WorkDatabase workDatabase = workManagerImpl.getWorkDatabase();
+                workDatabase.beginTransaction();
+                try {
+                    WorkSpecDao workSpecDao = workDatabase.workSpecDao();
+                    List<String> workSpecIds = workSpecDao.getAllUnfinishedWork();
+                    for (String workSpecId : workSpecIds) {
+                        cancel(workManagerImpl, workSpecId);
+                    }
+                    workDatabase.setTransactionSuccessful();
+                } finally {
+                    workDatabase.endTransaction();
+                }
+                // No need to call reschedule pending workers here as we just cancelled everything.
+            }
+        };
+    }
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java b/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
index b884ada..b49c005 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/workers/CombineContinuationsWorker.java
@@ -29,8 +29,8 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class CombineContinuationsWorker extends Worker {
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         setOutputData(getInputData());
-        return WorkerResult.SUCCESS;
+        return Result.SUCCESS;
     }
 }
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 b53e7bd..116e96c 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
@@ -63,11 +63,11 @@
     }
 
     @Override
-    public @NonNull WorkerResult doWork() {
+    public @NonNull Result doWork() {
         String className = getInputData().getString(ARGUMENT_CLASS_NAME, null);
         if (TextUtils.isEmpty(className)) {
             Log.d(TAG, "No worker to delegate to.");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
         // Instantiate the delegated worker. Use the same workSpecId, and the same Data
         // as this Worker's Data are a superset of the delegate's Worker's Data.
@@ -79,7 +79,7 @@
 
         if (mDelegate == null) {
             Log.d(TAG, "No worker to delegate to.");
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
 
         WorkDatabase workDatabase = getWorkDatabase();
@@ -87,7 +87,7 @@
         // We need to know what the real constraints are for the delegate.
         WorkSpec workSpec = workDatabase.workSpecDao().getWorkSpec(getId().toString());
         if (workSpec == null) {
-            return WorkerResult.FAILURE;
+            return Result.FAILURE;
         }
         WorkConstraintsTracker workConstraintsTracker =
                 new WorkConstraintsTracker(getApplicationContext(), this);
@@ -102,10 +102,10 @@
             // changes in constraints can cause the worker to throw RuntimeExceptions, and
             // that should cause a retry.
             try {
-                WorkerResult result = mDelegate.doWork();
+                Result result = mDelegate.doWork();
                 synchronized (mLock) {
                     if (mAreConstraintsUnmet) {
-                        return WorkerResult.RETRY;
+                        return Result.RETRY;
                     } else {
                         setOutputData(mDelegate.getOutputData());
                         return result;
@@ -117,16 +117,16 @@
                 synchronized (mLock) {
                     if (mAreConstraintsUnmet) {
                         Log.d(TAG, "Constraints were unmet, Retrying.");
-                        return WorkerResult.RETRY;
+                        return Result.RETRY;
                     } else {
-                        return WorkerResult.FAILURE;
+                        return Result.FAILURE;
                     }
                 }
             }
         } else {
             Log.d(TAG, String.format(
                     "Constraints not met for delegate %s. Requesting retry.", className));
-            return WorkerResult.RETRY;
+            return Result.RETRY;
         }
     }
 
diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/1.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/1.json
new file mode 100644
index 0000000..7cdb8a1
--- /dev/null
+++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/1.json
@@ -0,0 +1,363 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "f890a1c3a43db87aba55eaaf535b9e03",
+    "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": "alarmInfo",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`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 )",
+        "fields": [
+          {
+            "fieldPath": "workSpecId",
+            "columnName": "work_spec_id",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "alarmId",
+            "columnName": "alarm_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, \"f890a1c3a43db87aba55eaaf535b9e03\")"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json
new file mode 100644
index 0000000..00bc68d
--- /dev/null
+++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/2.json
@@ -0,0 +1,363 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "244d2ac5ecd0a7fb47b3755737585d7b",
+    "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, \"244d2ac5ecd0a7fb47b3755737585d7b\")"
+    ]
+  }
+}
\ No newline at end of file