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