More progress updates.
* Rename setProgress() to setProgressAsync() in ListenableWorker.
* Introduce setProgress() in CoroutineWorker and RxWorker.
* Add tests for setProgress.
Test: Added unit tests.
Change-Id: I92ee533c5050c566b4af58efafa53bdd3ef48667
diff --git a/work/workmanager-ktx/api/2.3.0-alpha01.txt b/work/workmanager-ktx/api/2.3.0-alpha01.txt
index 4ed72f8..ddfb009 100644
--- a/work/workmanager-ktx/api/2.3.0-alpha01.txt
+++ b/work/workmanager-ktx/api/2.3.0-alpha01.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/api/current.txt b/work/workmanager-ktx/api/current.txt
index 4ed72f8..ddfb009 100644
--- a/work/workmanager-ktx/api/current.txt
+++ b/work/workmanager-ktx/api/current.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt b/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt
index 2a2fd52..5b6423e 100644
--- a/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt
+++ b/work/workmanager-ktx/api/restricted_2.3.0-alpha01.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/api/restricted_current.txt b/work/workmanager-ktx/api/restricted_current.txt
index 2a2fd52..5b6423e 100644
--- a/work/workmanager-ktx/api/restricted_current.txt
+++ b/work/workmanager-ktx/api/restricted_current.txt
@@ -6,6 +6,7 @@
method public abstract suspend Object doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result> p);
method @Deprecated public kotlinx.coroutines.CoroutineDispatcher getCoroutineContext();
method public final void onStopped();
+ method public final suspend Object! setProgress(androidx.work.Data data, kotlin.coroutines.Continuation<? super kotlin.Unit> p);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result> startWork();
property @Deprecated public kotlinx.coroutines.CoroutineDispatcher coroutineContext;
}
diff --git a/work/workmanager-ktx/build.gradle b/work/workmanager-ktx/build.gradle
index 93c1a0e..0cee793 100644
--- a/work/workmanager-ktx/build.gradle
+++ b/work/workmanager-ktx/build.gradle
@@ -53,6 +53,9 @@
androidTestImplementation(ANDROIDX_TEST_CORE)
androidTestImplementation(ANDROIDX_TEST_RUNNER)
androidTestImplementation(ESPRESSO_CORE)
+ androidTestImplementation(MOCKITO_CORE, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
+ androidTestImplementation(DEXMAKER_MOCKITO, libs.exclude_bytebuddy) // DexMaker has its own MockMaker
+ androidTestImplementation(WORK_ARCH_ROOM_TESTING)
testImplementation(JUNIT)
}
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
index 6232be6..125f4e7 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -19,15 +19,18 @@
import android.content.Context
import android.util.Log
import androidx.arch.core.executor.ArchTaskExecutor
-
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
import androidx.test.filters.SmallTest
import androidx.work.impl.WorkDatabase
import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.WorkProgressUpdater
import androidx.work.impl.utils.futures.SettableFuture
import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import androidx.work.workers.ProgressUpdatingWorker
import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.runBlocking
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.instanceOf
import org.hamcrest.MatcherAssert.assertThat
@@ -35,6 +38,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import java.util.UUID
import java.util.concurrent.Executor
@@ -65,7 +73,7 @@
}
})
- context = ApplicationProvider.getApplicationContext() as android.content.Context
+ context = ApplicationProvider.getApplicationContext()
configuration = Configuration.Builder()
.setExecutor(SynchronousExecutor())
.setMinimumLoggingLevel(Log.DEBUG)
@@ -141,6 +149,44 @@
assertThat(worker.job.isCancelled, `is`(true))
}
+ @Test
+ @LargeTest
+ fun testProgressUpdates() {
+ val workerFactory = WorkerFactory.getDefaultWorkerFactory()
+ val progressUpdater = spy(WorkProgressUpdater(database, workManagerImpl.workTaskExecutor))
+ val workRequest = OneTimeWorkRequestBuilder<ProgressUpdatingWorker>().build()
+ database.workSpecDao().insertWorkSpec(workRequest.workSpec)
+ val worker = workerFactory.createWorkerWithDefaultFallback(
+ context,
+ ProgressUpdatingWorker::class.java.name,
+ WorkerParameters(
+ workRequest.id,
+ Data.EMPTY,
+ emptyList(),
+ WorkerParameters.RuntimeExtras(),
+ 1,
+ configuration.executor,
+ workManagerImpl.workTaskExecutor,
+ workerFactory,
+ progressUpdater
+ )
+ ) as ProgressUpdatingWorker
+
+ runBlocking {
+ val result = worker.doWork()
+ val captor = ArgumentCaptor.forClass(Data::class.java)
+ verify(progressUpdater, times(2))
+ .updateProgress(
+ any(Context::class.java),
+ any(UUID::class.java),
+ captor.capture()
+ )
+ assertThat(result, `is`(instanceOf(ListenableWorker.Result.Success::class.java)))
+ val recent = captor.allValues.lastOrNull()
+ assertThat(recent?.getInt(ProgressUpdatingWorker.Progress, 0), `is`(100))
+ }
+ }
+
class SynchronousExecutor : Executor {
override fun execute(command: Runnable) {
diff --git a/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/ProgressUpdatingWorker.kt b/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/ProgressUpdatingWorker.kt
new file mode 100644
index 0000000..f6d65ba
--- /dev/null
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/workers/ProgressUpdatingWorker.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2019 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.workers
+
+import android.content.Context
+import androidx.work.CoroutineWorker
+import androidx.work.Data
+import androidx.work.WorkerParameters
+import kotlinx.coroutines.delay
+
+class ProgressUpdatingWorker(context: Context, parameters: WorkerParameters) :
+ CoroutineWorker(context, parameters) {
+
+ companion object {
+ const val Progress = "Progress"
+ private const val delayDuration = 1L
+ }
+
+ override suspend fun doWork(): Result {
+ val firstUpdate = Data.Builder().putInt(Progress, 10).build()
+ val lastUpdate = Data.Builder().putInt(Progress, 100).build()
+ setProgress(firstUpdate)
+ delay(delayDuration)
+ setProgress(lastUpdate)
+ return Result.success()
+ }
+}
\ No newline at end of file
diff --git a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
index 56e5679..c1d12bb 100644
--- a/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
+++ b/work/workmanager-ktx/src/main/java/androidx/work/CoroutineWorker.kt
@@ -88,6 +88,16 @@
*/
abstract suspend fun doWork(): Result
+ /**
+ * Updates the progress for the [CoroutineWorker]. This is a suspending function unlike the
+ * [setProgressAsync] API which returns a [ListenableFuture].
+ *
+ * @param data The progress [Data]
+ */
+ suspend fun setProgress(data: Data) {
+ setProgressAsync(data).await()
+ }
+
final override fun onStopped() {
super.onStopped()
future.cancel(false)
diff --git a/work/workmanager-rxjava2/api/2.3.0-alpha01.txt b/work/workmanager-rxjava2/api/2.3.0-alpha01.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/2.3.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/2.3.0-alpha01.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/api/current.txt b/work/workmanager-rxjava2/api/current.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/current.txt
+++ b/work/workmanager-rxjava2/api/current.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt b/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt
+++ b/work/workmanager-rxjava2/api/restricted_2.3.0-alpha01.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/api/restricted_current.txt b/work/workmanager-rxjava2/api/restricted_current.txt
index e43f0e5..757ca4c 100644
--- a/work/workmanager-rxjava2/api/restricted_current.txt
+++ b/work/workmanager-rxjava2/api/restricted_current.txt
@@ -5,6 +5,7 @@
ctor public RxWorker(android.content.Context, androidx.work.WorkerParameters);
method @MainThread public abstract io.reactivex.Single<androidx.work.ListenableWorker.Result!> createWork();
method protected io.reactivex.Scheduler getBackgroundScheduler();
+ method public final io.reactivex.Single<java.lang.Void!> setProgress(androidx.work.Data);
method public com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
index de7fcf0..2200863 100644
--- a/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
+++ b/work/workmanager-rxjava2/src/main/java/androidx/work/RxWorker.java
@@ -117,10 +117,22 @@
@MainThread
public abstract @NonNull Single<Result> createWork();
+ /**
+ * Updates the progress for a {@link RxWorker}. This method returns a {@link Single} unlike the
+ * {@link ListenableWorker#setProgressAsync(Data)} API.
+ *
+ * @param data The progress {@link Data}
+ * @return The {@link Single}
+ */
+ @NonNull
+ public final Single<Void> setProgress(@NonNull Data data) {
+ return Single.fromFuture(setProgressAsync(data));
+ }
+
@Override
public void onStopped() {
super.onStopped();
- final SingleFutureAdapter observer = mSingleFutureObserverAdapter;
+ final SingleFutureAdapter<Result> observer = mSingleFutureObserverAdapter;
if (observer != null) {
observer.dispose();
mSingleFutureObserverAdapter = null;
diff --git a/work/workmanager/api/2.3.0-alpha01.txt b/work/workmanager/api/2.3.0-alpha01.txt
index 3656be3..a6cb786 100644
--- a/work/workmanager/api/2.3.0-alpha01.txt
+++ b/work/workmanager/api/2.3.0-alpha01.txt
@@ -143,7 +143,7 @@
method @RequiresApi(24) public final java.util.List<android.net.Uri!> getTriggeredContentUris();
method public final boolean isStopped();
method public void onStopped();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgress(androidx.work.Data);
+ method public final com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgressAsync(androidx.work.Data);
method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager/api/current.txt b/work/workmanager/api/current.txt
index 3656be3..a6cb786 100644
--- a/work/workmanager/api/current.txt
+++ b/work/workmanager/api/current.txt
@@ -143,7 +143,7 @@
method @RequiresApi(24) public final java.util.List<android.net.Uri!> getTriggeredContentUris();
method public final boolean isStopped();
method public void onStopped();
- method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgress(androidx.work.Data);
+ method public final com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> setProgressAsync(androidx.work.Data);
method @MainThread public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.ListenableWorker.Result!> startWork();
}
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index 72a7d5d..2203618 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -195,7 +195,7 @@
* Cancelling this future is a no-op.
*/
@NonNull
- public ListenableFuture<Void> setProgress(@NonNull Data data) {
+ public final ListenableFuture<Void> setProgressAsync(@NonNull Data data) {
return mWorkerParams.getProgressUpdater()
.updateProgress(getApplicationContext(), getId(), data);
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java b/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
index 38cd978..dc114ee 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
@@ -73,13 +73,14 @@
if (state == null) {
Logger.get().warning(TAG,
String.format(
- "Ignoring setProgress(...). WorkSpec (%s) does not exist.",
+ "Ignoring setProgressAsync(...). WorkSpec (%s) does not "
+ + "exist.",
workSpecId));
} else if (state.isFinished()) {
Logger.get().warning(TAG,
String.format(
- "Ignoring setProgress(...). WorkSpec (%s) has finished "
- + "execution.",
+ "Ignoring setProgressAsync(...). WorkSpec (%s) has "
+ + "finished execution.",
workSpecId));
} else {
WorkProgress progress = new WorkProgress(workSpecId, data);