Introduce `ProgressUpdater`.

* WorkProgressUpdater updates work progress on the task executor thread.
* Update version to 2.2.0-alpha01.

Test: Updated unit tests.
Change-Id: I28eb4fe182c708f838758155a122c59dd682a453
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 f20e6f0..6232be6 100644
--- a/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
+++ b/work/workmanager-ktx/src/androidTest/java/androidx/work/CoroutineWorkerTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.filters.SmallTest
 import androidx.work.impl.WorkDatabase
 import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.utils.futures.SettableFuture
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
 import kotlinx.coroutines.asCoroutineDispatcher
 import org.hamcrest.CoreMatchers.`is`
@@ -45,6 +46,7 @@
     private lateinit var configuration: Configuration
     private lateinit var database: WorkDatabase
     private lateinit var workManagerImpl: WorkManagerImpl
+    private lateinit var progressUpdater: ProgressUpdater
 
     @Before
     fun setUp() {
@@ -73,6 +75,12 @@
         )
         WorkManagerImpl.setDelegate(workManagerImpl)
         database = workManagerImpl.workDatabase
+        // No op
+        progressUpdater = ProgressUpdater { _, _, _ ->
+            val future = SettableFuture.create<Void>()
+            future.set(null)
+            future
+        }
     }
 
     @After
@@ -95,7 +103,8 @@
                 1,
                 configuration.executor,
                 workManagerImpl.workTaskExecutor,
-                workerFactory)) as SynchronousCoroutineWorker
+                workerFactory,
+                progressUpdater)) as SynchronousCoroutineWorker
 
         assertThat(worker.job.isCompleted, `is`(false))
 
@@ -124,7 +133,8 @@
                 1,
                 configuration.executor,
                 workManagerImpl.workTaskExecutor,
-                workerFactory)) as SynchronousCoroutineWorker
+                workerFactory,
+                progressUpdater)) as SynchronousCoroutineWorker
 
         assertThat(worker.job.isCancelled, `is`(false))
         worker.future.cancel(true)
diff --git a/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt b/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
index 54c9f47..bf07147 100644
--- a/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
+++ b/work/workmanager-rxjava2/src/test/java/androidx/work/RxWorkerTest.kt
@@ -27,7 +27,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.Mockito
+import org.mockito.Mockito.mock
 import java.util.UUID
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
@@ -105,7 +105,7 @@
             it.run()
         }
         val params = createWorkerParams(executor)
-        val worker = object : RxWorker(Mockito.mock(Context::class.java), params) {
+        val worker = object : RxWorker(mock(Context::class.java), params) {
             override fun createWork() = Single.just(result)
             override fun getBackgroundScheduler() = testScheduler
         }
@@ -115,7 +115,8 @@
     }
 
     private fun createWorkerParams(
-        executor: Executor = SynchronousExecutor()
+        executor: Executor = SynchronousExecutor(),
+        progressUpdater: ProgressUpdater = mock(ProgressUpdater::class.java)
     ) = WorkerParameters(
         UUID.randomUUID(),
         Data.EMPTY,
@@ -124,13 +125,14 @@
         1,
         executor,
         InstantWorkTaskExecutor(),
-        WorkerFactory.getDefaultWorkerFactory()
+        WorkerFactory.getDefaultWorkerFactory(),
+        progressUpdater
     )
 
     private fun Single<ListenableWorker.Result>.toWorker(
         params: WorkerParameters = createWorkerParams()
     ): RxWorker {
-        return object : RxWorker(Mockito.mock(Context::class.java), params) {
+        return object : RxWorker(mock(Context::class.java), params) {
             override fun createWork() = this@toWorker
         }
     }
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
index 501d81d..189d183 100644
--- a/work/workmanager-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestListenableWorkerBuilder.java
@@ -25,6 +25,7 @@
 import androidx.annotation.RestrictTo;
 import androidx.work.Data;
 import androidx.work.ListenableWorker;
+import androidx.work.ProgressUpdater;
 import androidx.work.WorkRequest;
 import androidx.work.WorkerFactory;
 import androidx.work.WorkerParameters;
@@ -56,6 +57,7 @@
     private WorkerFactory mWorkerFactory;
     private TaskExecutor mTaskExecutor;
     private Executor mExecutor;
+    private ProgressUpdater mProgressUpdater;
 
     TestListenableWorkerBuilder(@NonNull Context context, @NonNull Class<W> workerClass) {
         mContext = context;
@@ -69,6 +71,7 @@
         mWorkerFactory = WorkerFactory.getDefaultWorkerFactory();
         mTaskExecutor = new InstantWorkTaskExecutor();
         mExecutor = mTaskExecutor.getBackgroundExecutor();
+        mProgressUpdater = new TestProgressUpdater();
     }
 
     /**
@@ -160,6 +163,12 @@
         return mExecutor;
     }
 
+    @NonNull
+    @SuppressWarnings("KotlinPropertyAccess")
+    ProgressUpdater getProgressUpdater() {
+        return mProgressUpdater;
+    }
+
     /**
      * Sets the id for this unit of work.
      *
@@ -263,6 +272,19 @@
     }
 
     /**
+     * Sets the {@link ProgressUpdater} to be used to construct the
+     * {@link androidx.work.ListenableWorker}.
+     *
+     * @param updater The {@link ProgressUpdater} which can handle progress updates.
+     * @return The current {@link TestListenableWorkerBuilder}
+     */
+    @NonNull
+    public TestListenableWorkerBuilder setProgressUpdater(@NonNull ProgressUpdater updater) {
+        mProgressUpdater = updater;
+        return this;
+    }
+
+    /**
      * Sets the {@link Executor} that can be used to execute this unit of work.
      *
      * @param executor The {@link Executor}
@@ -292,7 +314,8 @@
                         // This is unused for ListenableWorker
                         getExecutor(),
                         getTaskExecutor(),
-                        getWorkerFactory()
+                        getWorkerFactory(),
+                        getProgressUpdater()
                 );
 
         WorkerFactory workerFactory = parameters.getWorkerFactory();
diff --git a/work/workmanager-testing/src/main/java/androidx/work/testing/TestProgressUpdater.java b/work/workmanager-testing/src/main/java/androidx/work/testing/TestProgressUpdater.java
new file mode 100644
index 0000000..23464b6
--- /dev/null
+++ b/work/workmanager-testing/src/main/java/androidx/work/testing/TestProgressUpdater.java
@@ -0,0 +1,54 @@
+/*
+ * 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.testing;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.work.Data;
+import androidx.work.Logger;
+import androidx.work.ProgressUpdater;
+import androidx.work.impl.utils.futures.SettableFuture;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.UUID;
+
+/**
+ * A {@link ProgressUpdater} which does nothing. Useful in the context of testing.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class TestProgressUpdater implements ProgressUpdater {
+
+    private static final String TAG = Logger.tagWithPrefix("TestProgressUpdater");
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> updateProgress(
+            @NonNull Context context,
+            @NonNull UUID id,
+            @NonNull Data data) {
+
+        Logger.get().info(TAG, String.format("Updating progress for %s (%s)", id, data));
+        SettableFuture<Void> future = SettableFuture.create();
+        future.set(null);
+        return future;
+    }
+}
diff --git a/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java b/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
index 332139c..551515e 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/DefaultWorkerFactoryTest.java
@@ -19,6 +19,7 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 
@@ -41,11 +42,13 @@
 
     private Context mContext;
     private WorkerFactory mDefaultWorkerFactory;
+    private ProgressUpdater mProgressUpdater;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
         mDefaultWorkerFactory = WorkerFactory.getDefaultWorkerFactory();
+        mProgressUpdater = mock(ProgressUpdater.class);
     }
 
     @Test
@@ -66,7 +69,8 @@
                         1,
                         executor,
                         new WorkManagerTaskExecutor(executor),
-                        mDefaultWorkerFactory));
+                        mDefaultWorkerFactory,
+                        mProgressUpdater));
         assertThat(worker, is(notNullValue()));
         assertThat(worker,
                 is(CoreMatchers.<ListenableWorker>instanceOf(TestWorker.class)));
diff --git a/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt b/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
index d84a892..12735f5 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
+++ b/work/workmanager/src/androidTest/java/androidx/work/DelegatingWorkerFactoryTest.kt
@@ -30,6 +30,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
 import java.util.UUID
 
 @RunWith(AndroidJUnit4::class)
@@ -38,11 +39,13 @@
 
     private lateinit var context: Context
     private lateinit var factory: DelegatingWorkerFactory
+    private lateinit var progressUpdater: ProgressUpdater
 
     @Before
     fun setUp() {
         context = ApplicationProvider.getApplicationContext()
         factory = DelegatingWorkerFactory()
+        progressUpdater = mock(ProgressUpdater::class.java)
     }
 
     @Test
@@ -52,7 +55,7 @@
 
         val request = OneTimeWorkRequest.from(TestWorker::class.java)
         insertWork(request)
-        val params: WorkerParameters = newWorkerParams(factory)
+        val params: WorkerParameters = newWorkerParams(factory, progressUpdater)
         val worker = factory.createWorkerWithDefaultFallback(
             context,
             TestWorker::class.java.name,
@@ -68,7 +71,7 @@
         factory = DelegatingWorkerFactory()
         val request = OneTimeWorkRequest.from(TestWorker::class.java)
         insertWork(request)
-        val params: WorkerParameters = newWorkerParams(factory)
+        val params: WorkerParameters = newWorkerParams(factory, progressUpdater)
         val worker = factory.createWorkerWithDefaultFallback(
             context,
             TestWorker::class.java.name,
@@ -79,16 +82,18 @@
         assertThat(worker, instanceOf(TestWorker::class.java))
     }
 
-    private fun newWorkerParams(factory: WorkerFactory) = WorkerParameters(
-        UUID.randomUUID(),
-        Data.EMPTY,
-        listOf<String>(),
-        WorkerParameters.RuntimeExtras(),
-        1,
-        SynchronousExecutor(),
-        WorkManagerTaskExecutor(SynchronousExecutor()),
-        factory
-    )
+    private fun newWorkerParams(factory: WorkerFactory, updater: ProgressUpdater) =
+        WorkerParameters(
+            UUID.randomUUID(),
+            Data.EMPTY,
+            listOf<String>(),
+            WorkerParameters.RuntimeExtras(),
+            1,
+            SynchronousExecutor(),
+            WorkManagerTaskExecutor(SynchronousExecutor()),
+            factory,
+            updater
+        )
 }
 
 class NoOpFactory : WorkerFactory() {
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 00014a9..20121dc 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -54,6 +54,7 @@
 import androidx.work.ListenableWorker;
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.PeriodicWorkRequest;
+import androidx.work.ProgressUpdater;
 import androidx.work.WorkerParameters;
 import androidx.work.impl.model.Dependency;
 import androidx.work.impl.model.DependencyDao;
@@ -99,6 +100,7 @@
     private DependencyDao mDependencyDao;
     private Context mContext;
     private Scheduler mMockScheduler;
+    private ProgressUpdater mMockProgressUpdater;
     private Executor mSynchronousExecutor = new SynchronousExecutor();
 
     @Before
@@ -112,6 +114,7 @@
         mWorkSpecDao = mDatabase.workSpecDao();
         mDependencyDao = mDatabase.dependencyDao();
         mMockScheduler = mock(Scheduler.class);
+        mMockProgressUpdater = mock(ProgressUpdater.class);
     }
 
     @Test
@@ -195,7 +198,8 @@
                                 1,
                                 mSynchronousExecutor,
                                 mWorkTaskExecutor,
-                                mConfiguration.getWorkerFactory()));
+                                mConfiguration.getWorkerFactory(),
+                                mMockProgressUpdater));
 
         WorkerWrapper workerWrapper = createBuilder(work.getStringId())
                 .withWorker(usedWorker)
@@ -770,7 +774,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getApplicationContext(), is(equalTo(mContext.getApplicationContext())));
@@ -796,7 +801,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getInputData().getString(key), is(expectedValue));
@@ -813,7 +819,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getInputData().size(), is(0));
@@ -839,7 +846,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getTags(), containsInAnyOrder("one", "two", "three"));
@@ -865,7 +873,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker.getTriggeredContentAuthorities(),
@@ -949,7 +958,8 @@
                                 1,
                                 Executors.newSingleThreadExecutor(),
                                 mWorkTaskExecutor,
-                                mConfiguration.getWorkerFactory()));
+                                mConfiguration.getWorkerFactory(),
+                                mMockProgressUpdater));
 
         WorkerWrapper workerWrapper =
                 createBuilder(work.getStringId())
@@ -990,7 +1000,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
         assertThat(worker, is(notNullValue()));
         assertThat(worker.isStopped(), is(false));
 
@@ -1022,7 +1033,8 @@
                         1,
                         mSynchronousExecutor,
                         mWorkTaskExecutor,
-                        mConfiguration.getWorkerFactory()));
+                        mConfiguration.getWorkerFactory(),
+                        mMockProgressUpdater));
         assertThat(worker, is(notNullValue()));
         assertThat(worker.isStopped(), is(false));
 
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 2ebd002..f897235 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
@@ -38,6 +38,7 @@
 import androidx.work.DatabaseTest;
 import androidx.work.ListenableWorker;
 import androidx.work.OneTimeWorkRequest;
+import androidx.work.ProgressUpdater;
 import androidx.work.WorkInfo;
 import androidx.work.WorkerFactory;
 import androidx.work.WorkerParameters;
@@ -85,6 +86,7 @@
     private Configuration mConfiguration;
     private TaskExecutor mWorkTaskExecutor;
     private Scheduler mScheduler;
+    private ProgressUpdater mProgressUpdater;
     private Trackers mTracker;
     private BatteryChargingTracker mBatteryChargingTracker;
     private BatteryNotLowTracker mBatteryNotLowTracker;
@@ -102,6 +104,7 @@
 
         mWorkManagerImpl = mock(WorkManagerImpl.class);
         mScheduler = mock(Scheduler.class);
+        mProgressUpdater = mock(ProgressUpdater.class);
         when(mWorkManagerImpl.getWorkDatabase()).thenReturn(mDatabase);
         when(mWorkManagerImpl.getWorkTaskExecutor()).thenReturn(mWorkTaskExecutor);
         when(mWorkManagerImpl.getConfiguration()).thenReturn(mConfiguration);
@@ -261,7 +264,8 @@
                         1,
                         executor,
                         mWorkTaskExecutor,
-                        workerFactory));
+                        workerFactory,
+                        mProgressUpdater));
 
         assertThat(worker, is(notNullValue()));
         assertThat(worker,
diff --git a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
index c86c3ae..72a7d5d 100644
--- a/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
+++ b/work/workmanager/src/main/java/androidx/work/ListenableWorker.java
@@ -188,6 +188,19 @@
     public abstract @NonNull ListenableFuture<Result> startWork();
 
     /**
+     * Updates {@link ListenableWorker} progress.
+     *
+     * @param data The progress {@link Data}
+     * @return A {@link ListenableFuture} which resolves after progress is persisted.
+     * Cancelling this future is a no-op.
+     */
+    @NonNull
+    public ListenableFuture<Void> setProgress(@NonNull Data data) {
+        return mWorkerParams.getProgressUpdater()
+                .updateProgress(getApplicationContext(), getId(), data);
+    }
+
+    /**
      * Returns {@code true} if this Worker has been told to stop.  This could be because of an
      * explicit cancellation signal by the user, or because the system has decided to preempt the
      * task. In these cases, the results of the work will be ignored by WorkManager and it is safe
diff --git a/work/workmanager/src/main/java/androidx/work/ProgressUpdater.java b/work/workmanager/src/main/java/androidx/work/ProgressUpdater.java
new file mode 100644
index 0000000..a090cd8
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/ProgressUpdater.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.UUID;
+
+/**
+ * Updates progress for a {@link androidx.work.ListenableWorker}.
+ */
+public interface ProgressUpdater {
+
+    /**
+     * @param context The application {@link Context}.
+     * @param id      The {@link UUID} identifying the {@link ListenableWorker}
+     * @param data    The progress {@link Data}
+     * @return The {@link ListenableFuture} which resolves after progress is persisted.
+     * Cancelling this future is a no-op.
+     */
+    @NonNull
+    ListenableFuture<Void> updateProgress(
+            @NonNull Context context,
+            @NonNull UUID id,
+            @NonNull Data data);
+}
diff --git a/work/workmanager/src/main/java/androidx/work/WorkerParameters.java b/work/workmanager/src/main/java/androidx/work/WorkerParameters.java
index 00d2f18..8c2f5a1 100644
--- a/work/workmanager/src/main/java/androidx/work/WorkerParameters.java
+++ b/work/workmanager/src/main/java/androidx/work/WorkerParameters.java
@@ -48,6 +48,7 @@
     private @NonNull Executor mBackgroundExecutor;
     private @NonNull TaskExecutor mWorkTaskExecutor;
     private @NonNull WorkerFactory mWorkerFactory;
+    private @NonNull ProgressUpdater mProgressUpdater;
 
     /**
      * @hide
@@ -61,7 +62,8 @@
             @IntRange(from = 0) int runAttemptCount,
             @NonNull Executor backgroundExecutor,
             @NonNull TaskExecutor workTaskExecutor,
-            @NonNull WorkerFactory workerFactory) {
+            @NonNull WorkerFactory workerFactory,
+            @NonNull ProgressUpdater progressUpdater) {
         mId = id;
         mInputData = inputData;
         mTags = new HashSet<>(tags);
@@ -70,6 +72,7 @@
         mBackgroundExecutor = backgroundExecutor;
         mWorkTaskExecutor = workTaskExecutor;
         mWorkerFactory = workerFactory;
+        mProgressUpdater = progressUpdater;
     }
 
     /**
@@ -173,6 +176,14 @@
     }
 
     /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public @NonNull ProgressUpdater getProgressUpdater() {
+        return mProgressUpdater;
+    }
+
+    /**
      * Extra runtime information for Workers.
      *
      * @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 de5b1c5..f24e179 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -47,6 +47,7 @@
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.model.WorkTagDao;
 import androidx.work.impl.utils.PackageManagerHelper;
+import androidx.work.impl.utils.WorkProgressUpdater;
 import androidx.work.impl.utils.futures.SettableFuture;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
 
@@ -224,7 +225,8 @@
                 mWorkSpec.runAttemptCount,
                 mConfiguration.getExecutor(),
                 mWorkTaskExecutor,
-                mConfiguration.getWorkerFactory());
+                mConfiguration.getWorkerFactory(),
+                new WorkProgressUpdater(mWorkDatabase, mWorkTaskExecutor));
 
         // Not always creating a worker here, as the WorkerWrapper.Builder can set a worker override
         // in test mode.
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
new file mode 100644
index 0000000..38cd978
--- /dev/null
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/WorkProgressUpdater.java
@@ -0,0 +1,100 @@
+/*
+ * 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.impl.utils;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.work.Data;
+import androidx.work.Logger;
+import androidx.work.ProgressUpdater;
+import androidx.work.WorkInfo.State;
+import androidx.work.impl.WorkDatabase;
+import androidx.work.impl.model.WorkProgress;
+import androidx.work.impl.utils.futures.SettableFuture;
+import androidx.work.impl.utils.taskexecutor.TaskExecutor;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.UUID;
+
+/**
+ * Persists {@link androidx.work.ListenableWorker} progress in a {@link WorkDatabase}.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public class WorkProgressUpdater implements ProgressUpdater {
+    // Synthetic access
+    static final String TAG = Logger.tagWithPrefix("WorkProgressUpdater");
+
+    // Synthetic access
+    final WorkDatabase mWorkDatabase;
+    // Synthetic access
+    final TaskExecutor mTaskExecutor;
+
+    public WorkProgressUpdater(
+            @NonNull WorkDatabase workDatabase,
+            @NonNull TaskExecutor taskExecutor) {
+        mWorkDatabase = workDatabase;
+        mTaskExecutor = taskExecutor;
+    }
+
+    @NonNull
+    @Override
+    public ListenableFuture<Void> updateProgress(
+            @NonNull final Context context,
+            @NonNull final UUID id,
+            @NonNull final Data data) {
+        final SettableFuture<Void> future = SettableFuture.create();
+        mTaskExecutor.executeOnBackgroundThread(new Runnable() {
+            @Override
+            public void run() {
+                String workSpecId = id.toString();
+                Logger.get().info(TAG, String.format("Updating progress for %s (%s)", id, data));
+                mWorkDatabase.beginTransaction();
+                try {
+                    State state = mWorkDatabase.workSpecDao().getState(workSpecId);
+                    if (state == null) {
+                        Logger.get().warning(TAG,
+                                String.format(
+                                        "Ignoring setProgress(...). WorkSpec (%s) does not exist.",
+                                        workSpecId));
+                    } else if (state.isFinished()) {
+                        Logger.get().warning(TAG,
+                                String.format(
+                                        "Ignoring setProgress(...). WorkSpec (%s) has finished "
+                                                + "execution.",
+                                        workSpecId));
+                    } else {
+                        WorkProgress progress = new WorkProgress(workSpecId, data);
+                        mWorkDatabase.workProgressDao().insert(progress);
+                    }
+                    future.set(null);
+                    mWorkDatabase.setTransactionSuccessful();
+                } catch (Throwable throwable) {
+                    Logger.get().error(TAG, "Error updating Worker progress", throwable);
+                    future.setException(throwable);
+                } finally {
+                    mWorkDatabase.endTransaction();
+                }
+            }
+        });
+        return future;
+    }
+}