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);