Merge changes I2bf26fd7,I6a29381b into androidx-main am: 7f6b7346a0

Original change: https://android-review.googlesource.com/c/platform/frameworks/support/+/2115799

Change-Id: Ibdde9782f98691efafafdb1a9aff478883514a30
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
index b2a0b97..3dd4813 100644
--- a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
+++ b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.filters.MediumTest
 import androidx.work.Configuration
 import androidx.work.OneTimeWorkRequest
+import androidx.work.impl.model.WorkGenerationalId
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.utils.SerialExecutorImpl
 import androidx.work.impl.utils.SynchronousExecutor
@@ -121,7 +122,9 @@
         `when`(taskParams.tag).thenReturn(request.workSpec.id)
         val result = mDispatcher.onRunTask(taskParams)
         assert(result == GcmNetworkManager.RESULT_SUCCESS)
-        verify(mWorkTimer, times(1)).startTimer(eq(request.workSpec.id), anyLong(), any())
-        verify(mWorkTimer, atLeastOnce()).stopTimer(eq(request.workSpec.id))
+        verify(mWorkTimer, times(1)).startTimer(eq(
+            WorkGenerationalId(request.workSpec.id, 0)
+        ), anyLong(), any())
+        verify(mWorkTimer, atLeastOnce()).stopTimer(eq(WorkGenerationalId(request.workSpec.id, 0)))
     }
 }
diff --git a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java
index 43acc2e..6dec44e 100644
--- a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java
+++ b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/GcmTaskConverter.java
@@ -23,6 +23,7 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import android.os.Build;
+import android.os.Bundle;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -51,11 +52,16 @@
     @VisibleForTesting
     public static final long EXECUTION_WINDOW_SIZE_IN_SECONDS = 5L;
 
+    static final String EXTRA_WORK_GENERATION = "androidx.work.impl.background.gcm.GENERATION";
+
     OneoffTask convert(@NonNull WorkSpec workSpec) {
+        Bundle extras = new Bundle();
+        extras.putInt(EXTRA_WORK_GENERATION, workSpec.getGeneration());
         OneoffTask.Builder builder = new OneoffTask.Builder();
         builder.setService(WorkManagerGcmService.class)
                 .setTag(workSpec.id)
                 .setUpdateCurrent(true)
+                .setExtras(extras)
                 .setPersisted(false);
 
         // Next run time is in seconds.
diff --git a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java
index 2e8dffd..d8483c7 100644
--- a/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java
+++ b/work/work-gcm/src/main/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcher.java
@@ -17,6 +17,7 @@
 package androidx.work.impl.background.gcm;
 
 
+import android.os.Bundle;
 import android.os.PowerManager;
 
 import androidx.annotation.MainThread;
@@ -30,6 +31,7 @@
 import androidx.work.impl.StartStopTokens;
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.WakeLocks;
 import androidx.work.impl.utils.WorkTimer;
@@ -96,10 +98,13 @@
             Logger.get().debug(TAG, "Bad request. No workSpecId.");
             return GcmNetworkManager.RESULT_FAILURE;
         }
-
-        WorkSpecExecutionListener listener = new WorkSpecExecutionListener(workSpecId,
+        Bundle extras = taskParams.getExtras();
+        int generation = extras != null
+                ? extras.getInt(GcmTaskConverter.EXTRA_WORK_GENERATION, 0) : 0;
+        WorkGenerationalId id = new WorkGenerationalId(workSpecId, generation);
+        WorkSpecExecutionListener listener = new WorkSpecExecutionListener(id,
                 mStartStopTokens);
-        StartStopToken startStopToken = mStartStopTokens.tokenFor(workSpecId);
+        StartStopToken startStopToken = mStartStopTokens.tokenFor(id);
         WorkSpecTimeLimitExceededListener timeLimitExceededListener =
                 new WorkSpecTimeLimitExceededListener(mWorkManagerImpl, startStopToken);
         Processor processor = mWorkManagerImpl.getProcessor();
@@ -108,7 +113,7 @@
         PowerManager.WakeLock wakeLock = WakeLocks.newWakeLock(
                 mWorkManagerImpl.getApplicationContext(), wakeLockTag);
         mWorkManagerImpl.startWork(startStopToken);
-        mWorkTimer.startTimer(workSpecId, AWAIT_TIME_IN_MILLISECONDS, timeLimitExceededListener);
+        mWorkTimer.startTimer(id, AWAIT_TIME_IN_MILLISECONDS, timeLimitExceededListener);
 
         try {
             wakeLock.acquire();
@@ -118,7 +123,7 @@
             return reschedule(workSpecId);
         } finally {
             processor.removeExecutionListener(listener);
-            mWorkTimer.stopTimer(workSpecId);
+            mWorkTimer.stopTimer(id);
             wakeLock.release();
         }
 
@@ -141,7 +146,7 @@
                     Logger.get().debug(TAG, "Returning RESULT_SUCCESS for WorkSpec " + workSpecId);
                     return GcmNetworkManager.RESULT_SUCCESS;
                 case FAILED:
-                    Logger.get().debug(TAG, "Returning RESULT_FAILURE for WorkSpec " +  workSpecId);
+                    Logger.get().debug(TAG, "Returning RESULT_FAILURE for WorkSpec " + workSpecId);
                     return GcmNetworkManager.RESULT_FAILURE;
                 default:
                     Logger.get().debug(TAG, "Rescheduling eligible work.");
@@ -185,23 +190,23 @@
         }
 
         @Override
-        public void onTimeLimitExceeded(@NonNull String workSpecId) {
-            Logger.get().debug(TAG, "WorkSpec time limit exceeded " + workSpecId);
+        public void onTimeLimitExceeded(@NonNull WorkGenerationalId id) {
+            Logger.get().debug(TAG, "WorkSpec time limit exceeded " + id);
             mWorkManager.stopWork(mStartStopToken);
         }
     }
 
     static class WorkSpecExecutionListener implements ExecutionListener {
         private static final String TAG = Logger.tagWithPrefix("WorkSpecExecutionListener");
-        private final String mWorkSpecId;
+        private final WorkGenerationalId mGenerationalId;
         private final CountDownLatch mLatch;
         private boolean mNeedsReschedule;
         private final StartStopTokens mStartStopTokens;
 
         WorkSpecExecutionListener(
-                @NonNull String workSpecId,
+                @NonNull WorkGenerationalId generationalId,
                 @NonNull StartStopTokens startStopTokens) {
-            mWorkSpecId = workSpecId;
+            mGenerationalId = generationalId;
             mStartStopTokens = startStopTokens;
             mLatch = new CountDownLatch(1);
             mNeedsReschedule = false;
@@ -216,12 +221,12 @@
         }
 
         @Override
-        public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
-            if (!mWorkSpecId.equals(workSpecId)) {
+        public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
+            if (!mGenerationalId.equals(id)) {
                 Logger.get().warning(TAG,
-                        "Notified for " + workSpecId + ", but was looking for " + mWorkSpecId);
+                        "Notified for " + id + ", but was looking for " + mGenerationalId);
             } else {
-                mStartStopTokens.remove(workSpecId);
+                mStartStopTokens.remove(id);
                 mNeedsReschedule = needsReschedule;
                 mLatch.countDown();
             }
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
index 2c4ffdd..99806fa 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteCoroutineWorkerTest.kt
@@ -163,7 +163,7 @@
             mTaskExecutor,
             mForegroundProcessor,
             mDatabase,
-            request.stringId
+            mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
         ).build()
     }
 }
diff --git a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
index 92c8e9f..337878a 100644
--- a/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
+++ b/work/work-multiprocess/src/androidTest/java/androidx/work/multiprocess/RemoteListenableWorkerTest.kt
@@ -188,7 +188,7 @@
             mTaskExecutor,
             mForegroundProcessor,
             mDatabase,
-            request.stringId
+            mDatabase.workSpecDao().getWorkSpec(request.stringId)!!
         ).build()
     }
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
index 28ac8f7..a9141a5 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ControlledWorkerWrapperTest.kt
@@ -37,9 +37,9 @@
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.Executor
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.concurrent.Executor
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -136,7 +136,7 @@
             taskExecutor,
             NoOpForegroundProcessor,
             workDatabase,
-            id
+            workDatabase.workSpecDao().getWorkSpec(id)!!
         ).build()
     }
 }
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java
index a7726a6..c994798 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTest.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
 import static org.mockito.Mockito.mock;
@@ -30,6 +32,7 @@
 import androidx.work.Configuration;
 import androidx.work.DatabaseTest;
 import androidx.work.OneTimeWorkRequest;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
 import androidx.work.worker.InfiniteTestWorker;
 
@@ -62,7 +65,7 @@
     @Test
     @SmallTest
     public void testStopWork_invalidWorkId() {
-        String id = "INVALID_WORK_ID";
+        WorkGenerationalId id = new WorkGenerationalId("INVALID_WORK_ID", 0);
         assertThat(mProcessor.stopWork(new StartStopToken(id)), is(false));
     }
 
@@ -70,7 +73,7 @@
     @SmallTest
     public void testStartWork_doesNotStartWorkTwice() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(InfiniteTestWorker.class).build();
-        String id = work.getStringId();
+        WorkGenerationalId id = generationalId(work.getWorkSpec());
         insertWork(work);
         assertThat(mProcessor.startWork(new StartStopToken(id)), is(true));
         assertThat(mProcessor.startWork(new StartStopToken(id)), is(false));
@@ -83,14 +86,14 @@
         insertWork(work);
 
         assertThat(mProcessor.hasWork(), is(false));
-        mProcessor.startWork(new StartStopToken(work.getStringId()));
+        mProcessor.startWork(new StartStopToken(generationalId(work.getWorkSpec())));
         assertThat(mProcessor.hasWork(), is(true));
     }
 
     @Test
     @SmallTest
     public void testDontCancelWhenNeedsReschedule() {
-        mProcessor.onExecuted("dummy", true);
-        verify(mMockScheduler, never()).cancel("dummy");
+        mProcessor.onExecuted(new WorkGenerationalId("foo", 0), true);
+        verify(mMockScheduler, never()).cancel("foo");
     }
 }
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
index ddb53cc..79d00d7 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/ProcessorTests.kt
@@ -28,8 +28,11 @@
 import androidx.work.ForegroundInfo
 import androidx.work.ListenableWorker
 import androidx.work.OneTimeWorkRequest
+import androidx.work.PeriodicWorkRequest
 import androidx.work.WorkerFactory
 import androidx.work.WorkerParameters
+import androidx.work.impl.model.WorkGenerationalId
+import androidx.work.impl.model.generationalId
 import androidx.work.impl.utils.SerialExecutorImpl
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
 import androidx.work.worker.LatchWorker
@@ -46,6 +49,7 @@
 import kotlinx.coroutines.runBlocking
 import org.junit.After
 import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
@@ -118,11 +122,11 @@
         val listener = ExecutionListener { id, _ ->
             if (!listenerCalled) {
                 listenerCalled = true
-                assertEquals(request1.workSpec.id, id)
+                assertEquals(request1.workSpec.id, id.workSpecId)
             }
         }
         processor.addExecutionListener(listener)
-        val startStopToken = StartStopToken(request1.workSpec.id)
+        val startStopToken = StartStopToken(WorkGenerationalId(request1.workSpec.id, 0))
         processor.startWork(startStopToken)
 
         val firstWorker = runBlocking { lastCreatedWorker.filterNotNull().first() }
@@ -140,7 +144,7 @@
         val executionFinished = CountDownLatch(1)
         processor.addExecutionListener { _, _ -> executionFinished.countDown() }
         // This would have previously failed trying to acquire a lock
-        processor.startWork(StartStopToken(request2.workSpec.id))
+        processor.startWork(StartStopToken(WorkGenerationalId(request2.workSpec.id, 0)))
         val secondWorker =
             runBlocking { lastCreatedWorker.filterNotNull().filter { it != firstWorker }.first() }
         (secondWorker as StopLatchWorker).countDown()
@@ -155,7 +159,9 @@
     fun testStartForegroundStopWork() {
         val request = OneTimeWorkRequest.Builder(LatchWorker::class.java).build()
         insertWork(request)
-        val startStopToken = StartStopToken(request.workSpec.id)
+        val startStopToken = StartStopToken(request.workSpec.generationalId())
+        val executionFinished = CountDownLatch(1)
+        processor.addExecutionListener { _, _ -> executionFinished.countDown() }
         processor.startWork(startStopToken)
 
         val channel = NotificationChannelCompat
@@ -170,13 +176,86 @@
             .setSmallIcon(androidx.core.R.drawable.notification_bg)
             .build()
         val info = ForegroundInfo(1, notification)
-        processor.startForeground(startStopToken.workSpecId, info)
+        processor.startForeground(startStopToken.id.workSpecId, info)
         // won't actually stopWork, because stopForeground should be used
         processor.stopWork(startStopToken)
-        processor.startWork(StartStopToken(request.workSpec.id))
-        assertTrue(processor.isEnqueued(startStopToken.workSpecId))
+        processor.startWork(StartStopToken(request.workSpec.generationalId()))
+        assertTrue(processor.isEnqueued(startStopToken.id.workSpecId))
         val firstWorker = runBlocking { lastCreatedWorker.filterNotNull().first() }
         (firstWorker as LatchWorker).mLatch.countDown()
+        assertTrue(executionFinished.await(3, TimeUnit.SECONDS))
+    }
+
+    @Test
+    @MediumTest
+    fun testStartOldGenerationDoesntStopCurrentWorker() {
+        val request = OneTimeWorkRequest.Builder(LatchWorker::class.java).build()
+        insertWork(request)
+        mDatabase.workSpecDao().incrementGeneration(request.stringId)
+        val token = StartStopToken(WorkGenerationalId(request.workSpec.id, 1))
+        processor.startWork(token)
+        val firstWorker = runBlocking { lastCreatedWorker.filterNotNull().first() }
+        var called = false
+        val oldGenerationListener = ExecutionListener { id, needsReschedule ->
+            called = true
+            assertEquals(WorkGenerationalId(request.workSpec.id, 0), id)
+            assertFalse(needsReschedule)
+        }
+        processor.addExecutionListener(oldGenerationListener)
+        processor.startWork(StartStopToken(WorkGenerationalId(request.workSpec.id, 0)))
+        assertTrue(called)
+        processor.removeExecutionListener(oldGenerationListener)
+        val executionFinished = CountDownLatch(1)
+        processor.addExecutionListener { _, _ -> executionFinished.countDown() }
+        (firstWorker as LatchWorker).mLatch.countDown()
+        assertTrue(executionFinished.await(3, TimeUnit.SECONDS))
+    }
+
+    @Test
+    @MediumTest
+    fun testStartNewGenerationDoesntStopCurrentWorker() {
+        val request = PeriodicWorkRequest.Builder(
+            LatchWorker::class.java, 10, TimeUnit.DAYS
+        ).build()
+        insertWork(request)
+        val token = StartStopToken(WorkGenerationalId(request.workSpec.id, 0))
+        processor.startWork(token)
+        val firstWorker = runBlocking { lastCreatedWorker.filterNotNull().first() }
+        var called = false
+        val oldGenerationListener = ExecutionListener { id, needsReschedule ->
+            called = true
+            assertEquals(WorkGenerationalId(request.workSpec.id, 1), id)
+            assertFalse(needsReschedule)
+        }
+        processor.addExecutionListener(oldGenerationListener)
+        mDatabase.workSpecDao().incrementGeneration(request.stringId)
+        processor.startWork(StartStopToken(WorkGenerationalId(request.workSpec.id, 1)))
+        assertTrue(called)
+        processor.removeExecutionListener(oldGenerationListener)
+        val executionFinished = CountDownLatch(1)
+        processor.addExecutionListener { _, _ -> executionFinished.countDown() }
+        (firstWorker as LatchWorker).mLatch.countDown()
+        assertTrue(executionFinished.await(3, TimeUnit.SECONDS))
+    }
+
+    @Test
+    @MediumTest
+    fun testOldGenerationDoesntStart() {
+        val request = OneTimeWorkRequest.Builder(LatchWorker::class.java).build()
+        insertWork(request)
+        mDatabase.workSpecDao().incrementGeneration(request.stringId)
+        val oldToken = StartStopToken(WorkGenerationalId(request.workSpec.id, 0))
+        var called = false
+        val oldGenerationListener = ExecutionListener { id, needsReschedule ->
+            called = true
+            // worker shouldn't have been created
+            assertEquals(null, lastCreatedWorker.value)
+            assertEquals(WorkGenerationalId(request.workSpec.id, 0), id)
+            assertFalse(needsReschedule)
+        }
+        processor.addExecutionListener(oldGenerationListener)
+        assertFalse(processor.startWork(oldToken))
+        assertTrue(called)
     }
 
     @After
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index f8825c8..be2df12 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -315,7 +315,7 @@
                 new InstantWorkTaskExecutor(),
                 foregroundProcessor,
                 mDatabase,
-                joinId)
+                workSpecDao.getWorkSpec(joinId))
                 .build()
                 .run();
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
index 6a00469..5879c17 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -41,6 +41,7 @@
 import androidx.work.WorkContinuation;
 import androidx.work.WorkInfo;
 import androidx.work.impl.background.greedy.GreedyScheduler;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -207,12 +208,12 @@
         }
 
         @Override
-        public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
+        public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
             synchronized (sLock) {
-                assertThat(mScheduledWorkSpecIds.contains(workSpecId), is(true));
-                mScheduledWorkSpecIds.remove(workSpecId);
+                assertThat(mScheduledWorkSpecIds.contains(id.getWorkSpecId()), is(true));
+                mScheduledWorkSpecIds.remove(id.getWorkSpecId());
             }
-            super.onExecuted(workSpecId, needsReschedule);
+            super.onExecuted(id, needsReschedule);
         }
     }
 }
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 8d55ac5..51f71d0 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -187,16 +187,6 @@
 
     @Test
     @SmallTest
-    public void testPermanentErrorWithInvalidWorkSpecId() {
-        final String invalidWorkSpecId = "INVALID_ID";
-        WorkerWrapper workerWrapper = createBuilder(invalidWorkSpecId).build();
-        FutureListener listener = createAndAddFutureListener(workerWrapper);
-        workerWrapper.run();
-        assertThat(listener.mResult, is(false));
-    }
-
-    @Test
-    @SmallTest
     public void testInvalidWorkerClassName() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         work.getWorkSpec().workerClassName = "dummy";
@@ -1237,7 +1227,8 @@
                 mWorkTaskExecutor,
                 mMockForegroundProcessor,
                 mDatabase,
-                work.getStringId()).build();
+                mWorkSpecDao.getWorkSpec(work.getStringId())
+        ).build();
 
         FutureListener listener = createAndAddFutureListener(workerWrapper);
         workerWrapper.run();
@@ -1286,7 +1277,7 @@
                 mWorkTaskExecutor,
                 mMockForegroundProcessor,
                 mDatabase,
-                work.getStringId()).build();
+                mWorkSpecDao.getWorkSpec(work.getStringId())).build();
 
         workerWrapper.interrupt();
         workerWrapper.run();
@@ -1313,7 +1304,7 @@
                 mWorkTaskExecutor,
                 mMockForegroundProcessor,
                 mDatabase,
-                workSpecId);
+                mWorkSpecDao.getWorkSpec(workSpecId));
     }
 
     private FutureListener createAndAddFutureListener(WorkerWrapper workerWrapper) {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
index bf591ce..f88681c 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/greedy/GreedySchedulerTest.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl.background.greedy;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.mock;
@@ -89,7 +91,7 @@
         mGreedyScheduler.schedule(workSpec);
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mWorkManagerImpl).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpec.id);
+        assertThat(captor.getValue().getId().getWorkSpecId()).isEqualTo(workSpec.id);
     }
 
     @Test
@@ -104,7 +106,7 @@
         // use `delayedStartWork()`.
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mWorkManagerImpl).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(periodicWork.getStringId());
+        assertThat(captor.getValue().getId().getWorkSpecId()).isEqualTo(periodicWork.getStringId());
     }
 
     @Test
@@ -149,7 +151,7 @@
         mGreedyScheduler.onAllConstraintsMet(Collections.singletonList(work.getWorkSpec()));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mWorkManagerImpl).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(work.getWorkSpec().id);
+        assertThat(captor.getValue().getId().getWorkSpecId()).isEqualTo(work.getWorkSpec().id);
     }
 
     @Test
@@ -161,7 +163,7 @@
         mGreedyScheduler.onAllConstraintsNotMet(Collections.singletonList(work.getWorkSpec()));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mWorkManagerImpl).stopWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(work.getWorkSpec().id);
+        assertThat(captor.getValue().getId().getWorkSpecId()).isEqualTo(work.getWorkSpec().id);
     }
 
     @Test
@@ -178,7 +180,7 @@
         verify(mMockWorkConstraintsTracker).replace(expected);
         reset(mMockWorkConstraintsTracker);
 
-        mGreedyScheduler.onExecuted(workSpec.id, false);
+        mGreedyScheduler.onExecuted(generationalId(workSpec), false);
         verify(mMockWorkConstraintsTracker).replace(Collections.<WorkSpec>emptySet());
     }
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java
index de25a72..a5d2d363 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/AlarmsTest.java
@@ -16,6 +16,9 @@
 
 package androidx.work.impl.background.systemalarm;
 
+import static androidx.work.impl.model.SystemIdInfoKt.systemIdInfo;
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.CoreMatchers.nullValue;
@@ -32,6 +35,7 @@
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.SystemIdInfo;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.worker.TestWorker;
 
 import org.junit.Before;
@@ -61,7 +65,7 @@
     public void testSetAlarm_noPreExistingAlarms() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
 
         Alarms.setAlarm(mContext, mWorkManager, workSpecId, mTriggerAt);
         SystemIdInfo systemIdInfo = mDatabase.systemIdInfoDao().getSystemIdInfo(workSpecId);
@@ -72,9 +76,9 @@
     public void testSetAlarm_withPreExistingAlarms() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
 
-        SystemIdInfo systemIdInfo = new SystemIdInfo(workSpecId, 1);
+        SystemIdInfo systemIdInfo = systemIdInfo(workSpecId, 1);
         mDatabase.systemIdInfoDao().insertSystemIdInfo(systemIdInfo);
 
         Alarms.setAlarm(mContext, mWorkManager, workSpecId, mTriggerAt);
@@ -87,9 +91,9 @@
     public void testCancelAlarm() {
         OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build();
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
 
-        SystemIdInfo systemIdInfo = new SystemIdInfo(workSpecId, 1);
+        SystemIdInfo systemIdInfo = systemIdInfo(workSpecId, 1);
         mDatabase.systemIdInfoDao().insertSystemIdInfo(systemIdInfo);
 
         Alarms.cancelAlarm(mContext, mWorkManager, workSpecId);
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index 4fe61a2..df6217c 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl.background.systemalarm;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.hamcrest.CoreMatchers.is;
@@ -59,6 +61,7 @@
 import androidx.work.impl.constraints.trackers.BatteryNotLowTracker;
 import androidx.work.impl.constraints.trackers.ConstraintTracker;
 import androidx.work.impl.constraints.trackers.Trackers;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.SerialExecutorImpl;
@@ -202,12 +205,12 @@
                 .build();
 
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
         final Intent intent = CommandHandler.createScheduleWorkIntent(mContext, workSpecId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, intent, START_ID));
         mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
-        assertThat(mDatabase.systemIdInfoDao().getSystemIdInfo(work.getStringId()),
+        assertThat(mDatabase.systemIdInfoDao().getSystemIdInfo(workSpecId),
                 is(notNullValue()));
     }
 
@@ -218,12 +221,12 @@
                 .setInitialDelay(TimeUnit.HOURS.toMillis(1), TimeUnit.MILLISECONDS)
                 .build();
         // DO NOT insert it into the DB.
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
         final Intent intent = CommandHandler.createScheduleWorkIntent(mContext, workSpecId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, intent, START_ID));
         mLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS);
-        assertThat(mDatabase.systemIdInfoDao().getSystemIdInfo(work.getStringId()),
+        assertThat(mDatabase.systemIdInfoDao().getSystemIdInfo(workSpecId),
                 is(nullValue()));
     }
 
@@ -235,7 +238,7 @@
                 .build();
 
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
         final Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, intent, START_ID));
@@ -243,7 +246,7 @@
         assertThat(mLatch.getCount(), is(0L));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mSpyProcessor, times(1)).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captor.getValue().getId()).isEqualTo(workSpecId);
     }
 
     @Test
@@ -253,7 +256,7 @@
                 .build();
 
         // Not inserting the workSpec.
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
         final Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, intent, START_ID));
@@ -278,8 +281,7 @@
                 .build();
 
         insertWork(work);
-        String workSpecId = work.getStringId();
-
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
         final Intent delayMet = CommandHandler.createDelayMetIntent(mContext, workSpecId);
         final Intent stopWork = CommandHandler.createStopWorkIntent(mContext, workSpecId);
 
@@ -294,11 +296,11 @@
         assertThat(mLatch.getCount(), is(0L));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mSpyProcessor, times(1)).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captor.getValue().getId()).isEqualTo(workSpecId);
 
         ArgumentCaptor<StartStopToken> captorStop = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mWorkManager, times(1)).stopWork(captorStop.capture());
-        assertThat(captorStop.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captorStop.getValue().getId()).isEqualTo(workSpecId);
     }
 
     @Test
@@ -308,7 +310,7 @@
                 .build();
 
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
 
         final Intent scheduleWork = CommandHandler.createDelayMetIntent(mContext, workSpecId);
         final Intent stopWork = CommandHandler.createStopWorkIntent(mContext, workSpecId);
@@ -324,11 +326,11 @@
         assertThat(mLatch.getCount(), is(0L));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mSpyProcessor, times(1)).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captor.getValue().getId()).isEqualTo(workSpecId);
 
         ArgumentCaptor<StartStopToken> captorStop = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mWorkManager, times(1)).stopWork(captorStop.capture());
-        assertThat(captorStop.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captorStop.getValue().getId()).isEqualTo(workSpecId);
     }
 
     @Test
@@ -338,7 +340,7 @@
                 .build();
 
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
 
         final Intent scheduleWork = CommandHandler.createDelayMetIntent(mContext, workSpecId);
         mMainThreadExecutor.execute(
@@ -348,7 +350,7 @@
         assertThat(mLatch.getCount(), is(0L));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mSpyProcessor, times(1)).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captor.getValue().getId()).isEqualTo(workSpecId);
 
         List<String> intentActions = mSpyDispatcher.getIntentActions();
         assertThat(intentActions,
@@ -378,7 +380,7 @@
                 .build();
 
         insertWork(work);
-        String workSpecId = work.getStringId();
+        WorkGenerationalId workSpecId = generationalId(work.getWorkSpec());
 
         final Intent scheduleWork = CommandHandler.createScheduleWorkIntent(mContext, workSpecId);
 
@@ -426,7 +428,7 @@
         assertThat(mLatch.getCount(), is(0L));
         ArgumentCaptor<StartStopToken> captor = ArgumentCaptor.forClass(StartStopToken.class);
         verify(mSpyProcessor, times(1)).startWork(captor.capture());
-        assertThat(captor.getValue().getWorkSpecId()).isEqualTo(workSpecId);
+        assertThat(captor.getValue().getId().getWorkSpecId()).isEqualTo(workSpecId);
     }
 
     @Test
@@ -443,7 +445,8 @@
 
         insertWork(work);
 
-        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, work.getStringId());
+        WorkGenerationalId workId = generationalId(work.getWorkSpec());
+        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, workId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, delayMet, START_ID));
 
@@ -482,7 +485,8 @@
 
         insertWork(work);
 
-        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, work.getStringId());
+        WorkGenerationalId workId = generationalId(work.getWorkSpec());
+        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, workId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, delayMet, START_ID));
 
@@ -535,7 +539,8 @@
 
         insertWork(work);
 
-        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, work.getStringId());
+        WorkGenerationalId workId = generationalId(work.getWorkSpec());
+        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, workId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, delayMet, START_ID));
 
@@ -714,7 +719,8 @@
 
         insertWork(work);
 
-        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, work.getStringId());
+        WorkGenerationalId workId = generationalId(work.getWorkSpec());
+        Intent delayMet = CommandHandler.createDelayMetIntent(mContext, workId);
         mMainThreadExecutor.execute(
                 new SystemAlarmDispatcher.AddRunnable(mSpyDispatcher, delayMet, START_ID));
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
index b80faa8..040b3aa 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemalarm/WorkTimerTest.java
@@ -26,6 +26,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.LargeTest;
 import androidx.work.impl.DefaultRunnableScheduler;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.utils.WorkTimer;
 
 import org.junit.Before;
@@ -35,7 +36,7 @@
 @RunWith(AndroidJUnit4.class)
 public class WorkTimerTest {
 
-    private static final String WORKSPEC_ID_1 = "1";
+    private static final WorkGenerationalId WORKSPEC_ID_1 = new WorkGenerationalId("1", 0);
 
     private WorkTimer mWorkTimer;
     private TestTimeLimitExceededListener mListener;
@@ -73,7 +74,7 @@
     public static class TestTimeLimitExceededListener implements
             WorkTimer.TimeLimitExceededListener {
         @Override
-        public void onTimeLimitExceeded(@NonNull String workSpecId) {
+        public void onTimeLimitExceeded(@NonNull WorkGenerationalId id) {
             // does nothing
         }
     }
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
index 0976478..8d80af8 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
@@ -20,8 +20,8 @@
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -120,7 +120,7 @@
         WorkManagerImpl.setDelegate(mWorkManagerImpl);
         mSystemJobServiceSpy = spy(new SystemJobService());
         doReturn(context).when(mSystemJobServiceSpy).getApplicationContext();
-        doNothing().when(mSystemJobServiceSpy).onExecuted(anyString(), anyBoolean());
+        doNothing().when(mSystemJobServiceSpy).onExecuted(any(), anyBoolean());
         mSystemJobServiceSpy.onCreate();
     }
 
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
index d8dd165..74e5983 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
@@ -34,6 +34,7 @@
 import androidx.work.impl.Processor
 import androidx.work.impl.Scheduler
 import androidx.work.impl.WorkDatabase
+import androidx.work.impl.model.WorkGenerationalId
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.StartStopToken
 import androidx.work.impl.constraints.WorkConstraintsCallback
@@ -116,7 +117,8 @@
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
         workDatabase.workSpecDao().insertWorkSpec(request.workSpec)
-        val intent = createStartForegroundIntent(context, request.stringId, metadata)
+        val intent = createStartForegroundIntent(context,
+            WorkGenerationalId(request.stringId, 0), metadata)
         dispatcher.onStartCommand(intent)
         verify(dispatcherCallback, times(1))
             .startForeground(eq(notificationId), eq(0), any<Notification>())
@@ -133,7 +135,8 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context, request.stringId, metadata)
+        val intent = createStartForegroundIntent(context,
+            WorkGenerationalId(request.stringId, 0), metadata)
         dispatcher.onStartCommand(intent)
         verify(dispatcherCallback, times(1))
             .startForeground(eq(notificationId), eq(0), any<Notification>())
@@ -150,7 +153,7 @@
 
     @Test
     fun testStartForeground() {
-        val workSpecId = "workSpecId"
+        val workSpecId = WorkGenerationalId("workSpecId", 0)
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
@@ -162,12 +165,12 @@
 
     @Test
     fun testNotify() {
-        val workSpecId = "workSpecId"
+        val workSpecId = WorkGenerationalId("workSpecId", 0)
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
         val intent = createNotifyIntent(context, workSpecId, metadata)
-        dispatcher.mCurrentForegroundWorkSpecId = "anotherWorkSpecId"
+        dispatcher.mCurrentForegroundId = WorkGenerationalId("anotherWorkSpecId", 0)
         dispatcher.onStartCommand(intent)
         verify(dispatcherCallback, times(1))
             .notify(eq(notificationId), any<Notification>())
@@ -175,24 +178,24 @@
 
     @Test
     fun testPromoteWorkSpecForStartForeground() {
-        val firstWorkSpecId = "first"
+        val firstWorkSpecId = WorkGenerationalId("first", 0)
         val firstId = 1
         val notification = mock(Notification::class.java)
         val firstInfo = ForegroundInfo(firstId, notification)
         val firstIntent = createNotifyIntent(context, firstWorkSpecId, firstInfo)
 
-        val secondWorkSpecId = "second"
+        val secondWorkSpecId = WorkGenerationalId("second", 0)
         val secondId = 2
         val secondInfo = ForegroundInfo(secondId, notification)
         val secondIntent = createNotifyIntent(context, secondWorkSpecId, secondInfo)
 
         dispatcher.onStartCommand(firstIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .startForeground(eq(firstId), eq(0), any<Notification>())
 
         dispatcher.onStartCommand(secondIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .notify(eq(secondId), any<Notification>())
         assertThat(dispatcher.mForegroundInfoById.count(), `is`(2))
@@ -212,35 +215,35 @@
 
     @Test
     fun promoteWorkSpecForStartForeground2() {
-        val firstWorkSpecId = "first"
+        val firstWorkSpecId = WorkGenerationalId("first", 0)
         val firstId = 1
         val notification = mock(Notification::class.java)
         val firstInfo = ForegroundInfo(firstId, notification)
         val firstIntent = createNotifyIntent(context, firstWorkSpecId, firstInfo)
 
-        val secondWorkSpecId = "second"
+        val secondWorkSpecId = WorkGenerationalId("second", 0)
         val secondId = 2
         val secondInfo = ForegroundInfo(secondId, notification)
         val secondIntent = createNotifyIntent(context, secondWorkSpecId, secondInfo)
 
-        val thirdWorkSpecId = "third"
+        val thirdWorkSpecId = WorkGenerationalId("third", 0)
         val thirdId = 3
         val thirdInfo = ForegroundInfo(thirdId, notification)
         val thirdIntent = createNotifyIntent(context, thirdWorkSpecId, thirdInfo)
 
         dispatcher.onStartCommand(firstIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .startForeground(eq(firstId), eq(0), any<Notification>())
 
         dispatcher.onStartCommand(secondIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .notify(eq(secondId), any<Notification>())
         assertThat(dispatcher.mForegroundInfoById.count(), `is`(2))
 
         dispatcher.onStartCommand(thirdIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .notify(eq(secondId), any<Notification>())
         assertThat(dispatcher.mForegroundInfoById.count(), `is`(3))
@@ -255,35 +258,35 @@
 
     @Test
     fun promoteWorkSpecForStartForeground3() {
-        val firstWorkSpecId = "first"
+        val firstWorkSpecId = WorkGenerationalId("first", 0)
         val firstId = 1
         val notification = mock(Notification::class.java)
         val firstInfo = ForegroundInfo(firstId, notification)
         val firstIntent = createNotifyIntent(context, firstWorkSpecId, firstInfo)
 
-        val secondWorkSpecId = "second"
+        val secondWorkSpecId = WorkGenerationalId("second", 0)
         val secondId = 2
         val secondInfo = ForegroundInfo(secondId, notification)
         val secondIntent = createNotifyIntent(context, secondWorkSpecId, secondInfo)
 
-        val thirdWorkSpecId = "third"
+        val thirdWorkSpecId = WorkGenerationalId("third", 0)
         val thirdId = 3
         val thirdInfo = ForegroundInfo(thirdId, notification)
         val thirdIntent = createNotifyIntent(context, thirdWorkSpecId, thirdInfo)
 
         dispatcher.onStartCommand(firstIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .startForeground(eq(firstId), eq(0), any<Notification>())
 
         dispatcher.onStartCommand(secondIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .notify(eq(secondId), any<Notification>())
         assertThat(dispatcher.mForegroundInfoById.count(), `is`(2))
 
         dispatcher.onStartCommand(thirdIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .notify(eq(secondId), any<Notification>())
         assertThat(dispatcher.mForegroundInfoById.count(), `is`(3))
@@ -297,14 +300,14 @@
     @Test
     @SdkSuppress(minSdkVersion = 29)
     fun testUpdateNotificationWithDifferentForegroundServiceType() {
-        val firstWorkSpecId = "first"
+        val firstWorkSpecId = WorkGenerationalId("first", 0)
         val firstId = 1
         val notification = mock(Notification::class.java)
         val firstInfo =
             ForegroundInfo(firstId, notification, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE)
         val firstIntent = createNotifyIntent(context, firstWorkSpecId, firstInfo)
 
-        val secondWorkSpecId = "second"
+        val secondWorkSpecId = WorkGenerationalId("second", 0)
         val secondId = 2
         val secondInfo = ForegroundInfo(secondId, notification, FOREGROUND_SERVICE_TYPE_LOCATION)
         val secondIntent = createNotifyIntent(context, secondWorkSpecId, secondInfo)
@@ -318,7 +321,7 @@
             )
 
         dispatcher.onStartCommand(secondIntent)
-        assertThat(dispatcher.mCurrentForegroundWorkSpecId, `is`(firstWorkSpecId))
+        assertThat(dispatcher.mCurrentForegroundId, `is`(firstWorkSpecId))
         verify(dispatcherCallback, times(1))
             .notify(eq(secondId), any<Notification>())
 
@@ -343,11 +346,14 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context, request.stringId, metadata)
+        val intent = createStartForegroundIntent(context,
+            WorkGenerationalId(request.stringId, 0), metadata)
         dispatcher.onStartCommand(intent)
         verify(tracker, times(1)).replace(setOf(request.workSpec))
         dispatcher.onAllConstraintsNotMet(listOf(request.workSpec))
-        verify(workManager, times(1)).stopForegroundWork(eq(request.workSpec.id))
+        verify(workManager, times(1)).stopForegroundWork(eq(
+            WorkGenerationalId(request.workSpec.id, 0)
+        ))
     }
 
     @Test
@@ -360,7 +366,8 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context, request.stringId, metadata)
+        val intent = createStartForegroundIntent(context,
+            WorkGenerationalId(request.workSpec.id, 0), metadata)
         dispatcher.onStartCommand(intent)
         verify(tracker, times(1)).replace(setOf(request.workSpec))
         val stopIntent = createCancelWorkIntent(context, request.stringId)
@@ -380,10 +387,11 @@
         val notificationId = 1
         val notification = mock(Notification::class.java)
         val metadata = ForegroundInfo(notificationId, notification)
-        val intent = createStartForegroundIntent(context, request.stringId, metadata)
+        val intent = createStartForegroundIntent(context,
+            WorkGenerationalId(request.stringId, 0), metadata)
         dispatcher.onStartCommand(intent)
         val stopWorkRunnable = StopWorkRunnable(
-            workManager, StartStopToken(request.stringId), false
+            workManager, StartStopToken(WorkGenerationalId(request.stringId, 0)), false
         )
         stopWorkRunnable.run()
         val state = workDatabase.workSpecDao().getState(request.stringId)
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
index 02049bc..01ce6aa 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
@@ -122,7 +122,7 @@
             taskExecutor,
             foregroundProcessor,
             workDatabase,
-            request.stringId
+            workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
         ).build()
 
         wrapper.run()
@@ -144,14 +144,13 @@
             taskExecutor,
             foregroundProcessor,
             workDatabase,
-            request.stringId
+            workDatabase.workSpecDao().getWorkSpec(request.stringId)!!
         ).build()
 
         wrapper.run()
         val future = wrapper.future as SettableFuture<Boolean>
         val latch = CountDownLatch(1)
-        future.addListener(
-            Runnable {
+        future.addListener({
                 assertThat(future.isDone, `is`(true))
                 latch.countDown()
             },
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
index 87eeb2b..1b5b5ac 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl.utils;
 
+import static androidx.work.impl.model.SystemIdInfoKt.systemIdInfo;
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
 import static androidx.work.impl.utils.ForceStopRunnable.MAX_ATTEMPTS;
 
 import static org.hamcrest.CoreMatchers.is;
@@ -47,7 +49,6 @@
 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.worker.TestWorker;
 
@@ -162,7 +163,8 @@
                 .build();
         WorkSpec workSpec = request.getWorkSpec();
         mWorkDatabase.workSpecDao().insertWorkSpec(workSpec);
-        mWorkDatabase.systemIdInfoDao().insertSystemIdInfo(new SystemIdInfo(workSpec.id, 0));
+        mWorkDatabase.systemIdInfoDao().insertSystemIdInfo(
+                systemIdInfo(generationalId(workSpec), 0));
         runnable.run();
         ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
         verify(mScheduler, times(1)).schedule(captor.capture());
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
index eeb8176..5bdc45e 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/workers/ConstraintTrackingWorkerTest.java
@@ -361,7 +361,8 @@
                 mWorkTaskExecutor,
                 mForegroundProcessor,
                 mDatabase,
-                mWork.getStringId());
+                mDatabase.workSpecDao().getWorkSpec(mWork.getStringId())
+        );
     }
 
     static class SpyingWorkerFactory extends WorkerFactory {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/ExecutionListener.java b/work/work-runtime/src/main/java/androidx/work/impl/ExecutionListener.java
index e37d803..d13e73c 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/ExecutionListener.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/ExecutionListener.java
@@ -19,6 +19,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
 import androidx.work.Worker;
+import androidx.work.impl.model.WorkGenerationalId;
 
 /**
  * Listener that reports the result of a {@link Worker}'s execution.
@@ -31,8 +32,8 @@
     /**
      * Called when a {@link Worker} has executed.
      *
-     * @param workSpecId      work id corresponding to the {@link Worker}
+     * @param id      work id corresponding to the {@link Worker}
      * @param needsReschedule {@code true} if work should be rescheduled according to backoff policy
      */
-    void onExecuted(@NonNull String workSpecId, boolean needsReschedule);
+    void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule);
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
index 3be9d53..1201feb 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/Processor.java
@@ -31,6 +31,8 @@
 import androidx.work.Logger;
 import androidx.work.WorkerParameters;
 import androidx.work.impl.foreground.ForegroundProcessor;
+import androidx.work.impl.model.WorkGenerationalId;
+import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.WakeLocks;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
 
@@ -113,18 +115,48 @@
     public boolean startWork(
             @NonNull StartStopToken startStopToken,
             @Nullable WorkerParameters.RuntimeExtras runtimeExtras) {
-        String id = startStopToken.getWorkSpecId();
+        WorkGenerationalId id = startStopToken.getId();
+        WorkSpec workSpec = mWorkDatabase.runInTransaction(
+                () -> mWorkDatabase.workSpecDao().getWorkSpec(id.getWorkSpecId())
+        );
+        if (workSpec == null) {
+            Logger.get().error(TAG, "Didn't find WorkSpec for id " + id);
+            runOnExecuted(id, false);
+            return false;
+        }
         WorkerWrapper workWrapper;
         synchronized (mLock) {
             // Work may get triggered multiple times if they have passing constraints
             // and new work with those constraints are added.
-            if (isEnqueued(id)) {
+            String workSpecId = id.getWorkSpecId();
+            if (isEnqueued(workSpecId)) {
                 // there must be another run if it is enqueued.
-                mWorkRuns.get(id).add(startStopToken);
-                Logger.get().debug(TAG, "Work " + id + " is already enqueued for processing");
+                Set<StartStopToken> tokens = mWorkRuns.get(workSpecId);
+                StartStopToken previousRun = tokens.iterator().next();
+                int previousRunGeneration = previousRun.getId().getGeneration();
+                if (previousRunGeneration == id.getGeneration()) {
+                    tokens.add(startStopToken);
+                    Logger.get().debug(TAG, "Work " + id + " is already enqueued for processing");
+                } else {
+                    // Implementation detail.
+                    // If previousRunGeneration > id.getGeneration(), then we don't have to do
+                    // anything because newer generation is already running
+                    //
+                    // Case of previousRunGeneration < id.getGeneration():
+                    // it should happen only in the case of the periodic worker,
+                    // so we let run a current Worker, and periodic worker will schedule
+                    // next iteration with updated work spec.
+                    runOnExecuted(id, false);
+                }
                 return false;
             }
 
+            if (workSpec.getGeneration() != id.getGeneration()) {
+                // not the latest generation, so ignoring this start request,
+                // new request with newer generation should arrive shortly.
+                runOnExecuted(id, false);
+                return false;
+            }
             workWrapper =
                     new WorkerWrapper.Builder(
                             mAppContext,
@@ -132,18 +164,18 @@
                             mWorkTaskExecutor,
                             this,
                             mWorkDatabase,
-                            id)
+                            workSpec)
                             .withSchedulers(mSchedulers)
                             .withRuntimeExtras(runtimeExtras)
                             .build();
             ListenableFuture<Boolean> future = workWrapper.getFuture();
             future.addListener(
-                    new FutureListener(this, id, future),
+                    new FutureListener(this, startStopToken.getId(), future),
                     mWorkTaskExecutor.getMainThreadExecutor());
-            mEnqueuedWorkMap.put(id, workWrapper);
+            mEnqueuedWorkMap.put(workSpecId, workWrapper);
             HashSet<StartStopToken> set = new HashSet<>();
             set.add(startStopToken);
-            mWorkRuns.put(id, set);
+            mWorkRuns.put(workSpecId, set);
         }
         mWorkTaskExecutor.getSerialTaskExecutor().execute(workWrapper);
         Logger.get().debug(TAG, getClass().getSimpleName() + ": processing " + id);
@@ -162,8 +194,8 @@
                     mForegroundLock.acquire();
                 }
                 mForegroundWorkMap.put(workSpecId, wrapper);
-                Intent intent = createStartForegroundIntent(mAppContext, workSpecId,
-                        foregroundInfo);
+                Intent intent = createStartForegroundIntent(mAppContext,
+                        wrapper.getWorkGenerationalId(), foregroundInfo);
                 ContextCompat.startForegroundService(mAppContext, intent);
             }
         }
@@ -172,10 +204,11 @@
     /**
      * Stops a unit of work running in the context of a foreground service.
      *
-     * @param id The work id to stop
+     * @param token The work to stop
      * @return {@code true} if the work was stopped successfully
      */
-    public boolean stopForegroundWork(@NonNull String id) {
+    public boolean stopForegroundWork(@NonNull StartStopToken token) {
+        String id = token.getId().getWorkSpecId();
         WorkerWrapper wrapper = null;
         synchronized (mLock) {
             Logger.get().debug(TAG, "Processor stopping foreground work " + id);
@@ -198,7 +231,7 @@
      * @return {@code true} if the work was stopped successfully
      */
     public boolean stopWork(@NonNull StartStopToken runId) {
-        String id = runId.getWorkSpecId();
+        String id = runId.getId().getWorkSpecId();
         WorkerWrapper wrapper = null;
         synchronized (mLock) {
             // Processor _only_ receives stopWork() requests from the schedulers that originally
@@ -333,21 +366,28 @@
     }
 
     @Override
-    public void onExecuted(
-            @NonNull final String workSpecId,
-            boolean needsReschedule) {
-
+    public void onExecuted(@NonNull final WorkGenerationalId id, boolean needsReschedule) {
         synchronized (mLock) {
-            mEnqueuedWorkMap.remove(workSpecId);
+            WorkerWrapper workerWrapper = mEnqueuedWorkMap.get(id.getWorkSpecId());
+            // can be called for another generation, so we shouldn't removed
+            if (workerWrapper != null && id.equals(workerWrapper.getWorkGenerationalId())) {
+                mEnqueuedWorkMap.remove(id.getWorkSpecId());
+            }
             Logger.get().debug(TAG,
-                    getClass().getSimpleName() + " " + workSpecId +
-                            " executed; reschedule = " + needsReschedule);
+                    getClass().getSimpleName() + " " + id.getWorkSpecId()
+                            + " executed; reschedule = " + needsReschedule);
             for (ExecutionListener executionListener : mOuterListeners) {
-                executionListener.onExecuted(workSpecId, needsReschedule);
+                executionListener.onExecuted(id, needsReschedule);
             }
         }
     }
 
+    private void runOnExecuted(@NonNull final WorkGenerationalId id, boolean needsReschedule) {
+        mWorkTaskExecutor.getMainThreadExecutor().execute(
+                () -> onExecuted(id, needsReschedule)
+        );
+    }
+
     private void stopForegroundService() {
         synchronized (mLock) {
             boolean hasForegroundWork = !mForegroundWorkMap.isEmpty();
@@ -395,15 +435,15 @@
     private static class FutureListener implements Runnable {
 
         private @NonNull ExecutionListener mExecutionListener;
-        private @NonNull String mWorkSpecId;
+        private @NonNull final WorkGenerationalId mWorkGenerationalId;
         private @NonNull ListenableFuture<Boolean> mFuture;
 
         FutureListener(
                 @NonNull ExecutionListener executionListener,
-                @NonNull String workSpecId,
+                @NonNull WorkGenerationalId workGenerationalId,
                 @NonNull ListenableFuture<Boolean> future) {
             mExecutionListener = executionListener;
-            mWorkSpecId = workSpecId;
+            mWorkGenerationalId = workGenerationalId;
             mFuture = future;
         }
 
@@ -416,7 +456,7 @@
                 // Should never really happen(?)
                 needsReschedule = true;
             }
-            mExecutionListener.onExecuted(mWorkSpecId, needsReschedule);
+            mExecutionListener.onExecuted(mWorkGenerationalId, needsReschedule);
         }
     }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/StartStopToken.kt b/work/work-runtime/src/main/java/androidx/work/impl/StartStopToken.kt
index 7d38ade..034a5a45 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/StartStopToken.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/StartStopToken.kt
@@ -15,6 +15,10 @@
  */
 package androidx.work.impl
 
+import androidx.work.impl.model.WorkGenerationalId
+import androidx.work.impl.model.WorkSpec
+import androidx.work.impl.model.generationalId
+
 // Impl note: it is **not** a data class on purpose.
 // Multiple schedulers can create `StartStopToken`-s for the same workSpecId, and `StartStopToken`
 // objects should be different. If two schedulers request to start a work with the same
@@ -25,22 +29,33 @@
 // be cancelled because the second scheduler tries to cancel already cancelled work.
 // So Processor class relies on StartStopToken-s being different and stores StartStopToken
 // with the same workSpecId in the set to differentiate between past and future run requests.
-class StartStopToken(val workSpecId: String)
+class StartStopToken(val id: WorkGenerationalId)
 
 class StartStopTokens {
     private val lock = Any()
-    private val runs = mutableMapOf<String, StartStopToken>()
+    private val runs = mutableMapOf<WorkGenerationalId, StartStopToken>()
 
-    fun tokenFor(workSpecId: String): StartStopToken {
+    fun tokenFor(id: WorkGenerationalId): StartStopToken {
         return synchronized(lock) {
-            val startStopToken = StartStopToken(workSpecId)
-            runs.getOrPut(workSpecId) { startStopToken }
+            val startStopToken = StartStopToken(id)
+            runs.getOrPut(id) { startStopToken }
         }
     }
 
-    fun remove(workSpecId: String): StartStopToken? {
+    fun remove(id: WorkGenerationalId): StartStopToken? {
         return synchronized(lock) {
-            runs.remove(workSpecId)
+            runs.remove(id)
         }
     }
+
+    fun remove(workSpecId: String): List<StartStopToken> {
+        return synchronized(lock) {
+            val toRemove = runs.filterKeys { it.workSpecId == workSpecId }
+            toRemove.keys.forEach { runs.remove(it) }
+            toRemove.values.toList()
+        }
+    }
+
+    fun tokenFor(spec: WorkSpec) = tokenFor(spec.generationalId())
+    fun remove(spec: WorkSpec) = remove(spec.generationalId())
 }
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabase.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabase.kt
index dc4d34e..584f910 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabase.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkDatabase.kt
@@ -63,9 +63,10 @@
         WorkName::class, WorkProgress::class, Preference::class],
     autoMigrations = [
         AutoMigration(from = 13, to = 14),
-        AutoMigration(from = 14, to = 15, spec = AutoMigration_14_15::class)
+        AutoMigration(from = 14, to = 15, spec = AutoMigration_14_15::class),
+        AutoMigration(from = 15, to = 16),
     ],
-    version = 15
+    version = 16
 )
 @TypeConverters(value = [Data::class, WorkTypeConverters::class])
 abstract class WorkDatabase : RoomDatabase() {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 70e1767..13e8901 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -54,6 +54,7 @@
 import androidx.work.impl.background.systemjob.SystemJobScheduler;
 import androidx.work.impl.constraints.trackers.Trackers;
 import androidx.work.impl.model.RawWorkInfoDao;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.CancelWorkRunnable;
@@ -695,14 +696,14 @@
     }
 
     /**
-     * @param workSpecId The {@link WorkSpec} id to stop when running in the context of a
+     * @param id The {@link WorkSpec} id to stop when running in the context of a
      *                   foreground service.
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public void stopForegroundWork(@NonNull String workSpecId) {
+    public void stopForegroundWork(@NonNull WorkGenerationalId id) {
         mWorkTaskExecutor.executeOnTaskThread(new StopWorkRunnable(this,
-                new StartStopToken(workSpecId), true));
+                new StartStopToken(id), true));
     }
 
     /**
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java
index f4c83fb..78f014f 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -23,6 +23,7 @@
 import static androidx.work.WorkInfo.State.RUNNING;
 import static androidx.work.WorkInfo.State.SUCCEEDED;
 import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -44,6 +45,7 @@
 import androidx.work.impl.background.systemalarm.RescheduleReceiver;
 import androidx.work.impl.foreground.ForegroundProcessor;
 import androidx.work.impl.model.DependencyDao;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.model.WorkTagDao;
@@ -78,7 +80,7 @@
 
     // Avoid Synthetic accessor
     Context mAppContext;
-    private String mWorkSpecId;
+    private final String mWorkSpecId;
     private List<Scheduler> mSchedulers;
     private WorkerParameters.RuntimeExtras mRuntimeExtras;
     // Avoid Synthetic accessor
@@ -116,7 +118,8 @@
         mAppContext = builder.mAppContext;
         mWorkTaskExecutor = builder.mWorkTaskExecutor;
         mForegroundProcessor = builder.mForegroundProcessor;
-        mWorkSpecId = builder.mWorkSpecId;
+        mWorkSpec = builder.mWorkSpec;
+        mWorkSpecId = mWorkSpec.id;
         mSchedulers = builder.mSchedulers;
         mRuntimeExtras = builder.mRuntimeExtras;
         mWorker = builder.mWorker;
@@ -128,6 +131,11 @@
         mWorkTagDao = mWorkDatabase.workTagDao();
     }
 
+    @NonNull
+    public WorkGenerationalId getWorkGenerationalId() {
+        return generationalId(mWorkSpec);
+    }
+
     public @NonNull ListenableFuture<Boolean> getFuture() {
         return mFuture;
     }
@@ -147,16 +155,6 @@
 
         mWorkDatabase.beginTransaction();
         try {
-            mWorkSpec = mWorkSpecDao.getWorkSpec(mWorkSpecId);
-            if (mWorkSpec == null) {
-                Logger.get().error(
-                        TAG,
-                        "Didn't find WorkSpec for id " + mWorkSpecId);
-                resolve(false);
-                mWorkDatabase.setTransactionSuccessful();
-                return;
-            }
-
             // Do a quick check to make sure we don't need to bail out in case this work is already
             // running, finished, or is blocked.
             if (mWorkSpec.state != ENQUEUED) {
@@ -639,7 +637,7 @@
         @NonNull TaskExecutor mWorkTaskExecutor;
         @NonNull Configuration mConfiguration;
         @NonNull WorkDatabase mWorkDatabase;
-        @NonNull String mWorkSpecId;
+        @NonNull WorkSpec mWorkSpec;
         List<Scheduler> mSchedulers;
         @NonNull
         WorkerParameters.RuntimeExtras mRuntimeExtras = new WorkerParameters.RuntimeExtras();
@@ -649,13 +647,13 @@
                 @NonNull TaskExecutor workTaskExecutor,
                 @NonNull ForegroundProcessor foregroundProcessor,
                 @NonNull WorkDatabase database,
-                @NonNull String workSpecId) {
+                @NonNull WorkSpec workSpec) {
             mAppContext = context.getApplicationContext();
             mWorkTaskExecutor = workTaskExecutor;
             mForegroundProcessor = foregroundProcessor;
             mConfiguration = configuration;
             mWorkDatabase = database;
-            mWorkSpecId = workSpecId;
+            mWorkSpec = workSpec;
         }
 
         /**
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
index 9b74a63..e1c9c42 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/greedy/GreedyScheduler.java
@@ -18,6 +18,8 @@
 
 import static android.os.Build.VERSION.SDK_INT;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import android.content.Context;
 import android.text.TextUtils;
 
@@ -36,6 +38,7 @@
 import androidx.work.impl.constraints.WorkConstraintsTracker;
 import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
 import androidx.work.impl.constraints.trackers.Trackers;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.ProcessUtils;
 
@@ -144,7 +147,7 @@
                     }
                 } else {
                     Logger.get().debug(TAG, "Starting work for " + workSpec.id);
-                    mWorkManagerImpl.startWork(mStartStopTokens.tokenFor(workSpec.id));
+                    mWorkManagerImpl.startWork(mStartStopTokens.tokenFor(workSpec));
                 }
             }
         }
@@ -183,27 +186,26 @@
             mDelayedWorkTracker.unschedule(workSpecId);
         }
         // onExecutionCompleted does the cleanup.
-        StartStopToken runId = mStartStopTokens.remove(workSpecId);
-        if (runId != null) {
-            mWorkManagerImpl.stopWork(runId);
+        for (StartStopToken id: mStartStopTokens.remove(workSpecId)) {
+            mWorkManagerImpl.stopWork(id);
         }
     }
 
     @Override
     public void onAllConstraintsMet(@NonNull List<WorkSpec> workSpecs) {
         for (WorkSpec workSpec : workSpecs) {
-            String workSpecId = workSpec.id;
-            Logger.get().debug(TAG, "Constraints met: Scheduling work ID " + workSpecId);
-            mWorkManagerImpl.startWork(mStartStopTokens.tokenFor(workSpecId));
+            WorkGenerationalId id = generationalId(workSpec);
+            Logger.get().debug(TAG, "Constraints met: Scheduling work ID " + id);
+            mWorkManagerImpl.startWork(mStartStopTokens.tokenFor(id));
         }
     }
 
     @Override
     public void onAllConstraintsNotMet(@NonNull List<WorkSpec> workSpecs) {
         for (WorkSpec workSpec : workSpecs) {
-            String workSpecId = workSpec.id;
-            Logger.get().debug(TAG, "Constraints not met: Cancelling work ID " + workSpecId);
-            StartStopToken runId = mStartStopTokens.remove(workSpecId);
+            WorkGenerationalId id = generationalId(workSpec);
+            Logger.get().debug(TAG, "Constraints not met: Cancelling work ID " + id);
+            StartStopToken runId = mStartStopTokens.remove(id);
             if (runId != null) {
                 mWorkManagerImpl.stopWork(runId);
             }
@@ -211,21 +213,21 @@
     }
 
     @Override
-    public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
-        mStartStopTokens.remove(workSpecId);
-        removeConstraintTrackingFor(workSpecId);
+    public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
+        mStartStopTokens.remove(id);
+        removeConstraintTrackingFor(id);
         // onExecuted does not need to worry about unscheduling WorkSpecs with the mDelayedTracker.
         // This is because, after onExecuted(), all schedulers are asked to cancel.
     }
 
-    private void removeConstraintTrackingFor(@NonNull String workSpecId) {
+    private void removeConstraintTrackingFor(@NonNull WorkGenerationalId id) {
         synchronized (mLock) {
             // This is synchronized because onExecuted is on the main thread but
             // Schedulers#schedule() can modify the list of mConstrainedWorkSpecs on the task
             // executor thread.
             for (WorkSpec constrainedWorkSpec : mConstrainedWorkSpecs) {
-                if (constrainedWorkSpec.id.equals(workSpecId)) {
-                    Logger.get().debug(TAG, "Stopping tracking for " + workSpecId);
+                if (generationalId(constrainedWorkSpec).equals(id)) {
+                    Logger.get().debug(TAG, "Stopping tracking for " + id);
                     mConstrainedWorkSpecs.remove(constrainedWorkSpec);
                     mWorkConstraintsTracker.replace(mConstrainedWorkSpecs);
                     break;
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
index c3d5bbf..2f20c1a 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/Alarms.java
@@ -18,6 +18,8 @@
 
 import static android.app.AlarmManager.RTC_WAKEUP;
 
+import static androidx.work.impl.model.SystemIdInfoKt.systemIdInfo;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -33,6 +35,7 @@
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.SystemIdInfo;
 import androidx.work.impl.model.SystemIdInfoDao;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.IdGenerator;
 
@@ -51,27 +54,27 @@
      *
      * @param context         The application {@link Context}.
      * @param workManager     The instance of {@link WorkManagerImpl}.
-     * @param workSpecId      The {@link WorkSpec} identifier.
+     * @param id      The {@link WorkGenerationalId} identifier.
      * @param triggerAtMillis Determines when to trigger the Alarm.
      */
     public static void setAlarm(
             @NonNull Context context,
             @NonNull WorkManagerImpl workManager,
-            @NonNull String workSpecId,
+            @NonNull WorkGenerationalId id,
             long triggerAtMillis) {
 
         WorkDatabase workDatabase = workManager.getWorkDatabase();
         SystemIdInfoDao systemIdInfoDao = workDatabase.systemIdInfoDao();
-        SystemIdInfo systemIdInfo = systemIdInfoDao.getSystemIdInfo(workSpecId);
+        SystemIdInfo systemIdInfo = systemIdInfoDao.getSystemIdInfo(id);
         if (systemIdInfo != null) {
-            cancelExactAlarm(context, workSpecId, systemIdInfo.systemId);
-            setExactAlarm(context, workSpecId, systemIdInfo.systemId, triggerAtMillis);
+            cancelExactAlarm(context, id, systemIdInfo.systemId);
+            setExactAlarm(context, id, systemIdInfo.systemId, triggerAtMillis);
         } else {
             IdGenerator idGenerator = new IdGenerator(workDatabase);
             int alarmId = idGenerator.nextAlarmManagerId();
-            SystemIdInfo newSystemIdInfo = new SystemIdInfo(workSpecId, alarmId);
+            SystemIdInfo newSystemIdInfo = systemIdInfo(id, alarmId);
             systemIdInfoDao.insertSystemIdInfo(newSystemIdInfo);
-            setExactAlarm(context, workSpecId, alarmId, triggerAtMillis);
+            setExactAlarm(context, id, alarmId, triggerAtMillis);
         }
     }
 
@@ -80,45 +83,45 @@
      *
      * @param context     The application {@link Context}.
      * @param workManager The instance of {@link WorkManagerImpl}.
-     * @param workSpecId  The {@link WorkSpec} identifier.
+     * @param id  The {@link WorkSpec} identifier.
      */
     public static void cancelAlarm(
             @NonNull Context context,
             @NonNull WorkManagerImpl workManager,
-            @NonNull String workSpecId) {
-
+            @NonNull WorkGenerationalId id) {
         WorkDatabase workDatabase = workManager.getWorkDatabase();
         SystemIdInfoDao systemIdInfoDao = workDatabase.systemIdInfoDao();
-        SystemIdInfo systemIdInfo = systemIdInfoDao.getSystemIdInfo(workSpecId);
+        SystemIdInfo systemIdInfo = systemIdInfoDao.getSystemIdInfo(id);
         if (systemIdInfo != null) {
-            cancelExactAlarm(context, workSpecId, systemIdInfo.systemId);
+            cancelExactAlarm(context, id, systemIdInfo.systemId);
             Logger.get().debug(TAG,
-                    "Removing SystemIdInfo for workSpecId (" + workSpecId + ")");
-            systemIdInfoDao.removeSystemIdInfo(workSpecId);
+                    "Removing SystemIdInfo for workSpecId (" + id + ")");
+            systemIdInfoDao.removeSystemIdInfo(id);
         }
     }
 
     private static void cancelExactAlarm(
             @NonNull Context context,
-            @NonNull String workSpecId,
+            @NonNull WorkGenerationalId id,
             int alarmId) {
-
         AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
-        Intent delayMet = CommandHandler.createDelayMetIntent(context, workSpecId);
+        Intent delayMet = CommandHandler.createDelayMetIntent(context, id);
         int flags = PendingIntent.FLAG_NO_CREATE;
         if (Build.VERSION.SDK_INT >= 23) {
             flags |= PendingIntent.FLAG_IMMUTABLE;
         }
         PendingIntent pendingIntent = PendingIntent.getService(context, alarmId, delayMet, flags);
         if (pendingIntent != null && alarmManager != null) {
-            Logger.get().debug(TAG, "Cancelling existing alarm with (workSpecId, systemId) (" + workSpecId + ", " + alarmId + ")");
+            Logger.get().debug(TAG,
+                    "Cancelling existing alarm with (workSpecId, systemId) (" + id
+                            + ", " + alarmId + ")");
             alarmManager.cancel(pendingIntent);
         }
     }
 
     private static void setExactAlarm(
             @NonNull Context context,
-            @NonNull String workSpecId,
+            @NonNull WorkGenerationalId id,
             int alarmId,
             long triggerAtMillis) {
 
@@ -127,7 +130,7 @@
         if (Build.VERSION.SDK_INT >= 23) {
             flags |= PendingIntent.FLAG_IMMUTABLE;
         }
-        Intent delayMet = CommandHandler.createDelayMetIntent(context, workSpecId);
+        Intent delayMet = CommandHandler.createDelayMetIntent(context, id);
         PendingIntent pendingIntent = PendingIntent.getService(context, alarmId, delayMet, flags);
         if (alarmManager != null) {
             if (Build.VERSION.SDK_INT >= 19) {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
index bda8a9a..30abc8ccb 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/CommandHandler.java
@@ -30,10 +30,13 @@
 import androidx.work.impl.StartStopTokens;
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -56,24 +59,36 @@
 
     // keys
     private static final String KEY_WORKSPEC_ID = "KEY_WORKSPEC_ID";
+    private static final String KEY_WORKSPEC_GENERATION = "KEY_WORKSPEC_GENERATION";
     private static final String KEY_NEEDS_RESCHEDULE = "KEY_NEEDS_RESCHEDULE";
 
     // constants
     static final long WORK_PROCESSING_TIME_IN_MS = 10 * 60 * 1000L;
 
     // utilities
-    static Intent createScheduleWorkIntent(@NonNull Context context, @NonNull String workSpecId) {
+    static Intent createScheduleWorkIntent(@NonNull Context context,
+            @NonNull WorkGenerationalId id) {
         Intent intent = new Intent(context, SystemAlarmService.class);
         intent.setAction(ACTION_SCHEDULE_WORK);
-        intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
+        return writeWorkGenerationalId(intent, id);
+    }
+
+    private static Intent writeWorkGenerationalId(@NonNull Intent intent,
+            @NonNull WorkGenerationalId id) {
+        intent.putExtra(KEY_WORKSPEC_ID, id.getWorkSpecId());
+        intent.putExtra(KEY_WORKSPEC_GENERATION, id.getGeneration());
         return intent;
     }
 
-    static Intent createDelayMetIntent(@NonNull Context context, @NonNull String workSpecId) {
+    static WorkGenerationalId readWorkGenerationalId(@NonNull Intent intent) {
+        return new WorkGenerationalId(intent.getStringExtra(KEY_WORKSPEC_ID),
+                intent.getIntExtra(KEY_WORKSPEC_GENERATION, 0));
+    }
+
+    static Intent createDelayMetIntent(@NonNull Context context, @NonNull WorkGenerationalId id) {
         Intent intent = new Intent(context, SystemAlarmService.class);
         intent.setAction(ACTION_DELAY_MET);
-        intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
-        return intent;
+        return writeWorkGenerationalId(intent, id);
     }
 
     static Intent createStopWorkIntent(@NonNull Context context, @NonNull String workSpecId) {
@@ -82,6 +97,11 @@
         intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
         return intent;
     }
+    static Intent createStopWorkIntent(@NonNull Context context, @NonNull WorkGenerationalId id) {
+        Intent intent = new Intent(context, SystemAlarmService.class);
+        intent.setAction(ACTION_STOP_WORK);
+        return writeWorkGenerationalId(intent, id);
+    }
 
     static Intent createConstraintsChangedIntent(@NonNull Context context) {
         Intent intent = new Intent(context, SystemAlarmService.class);
@@ -97,19 +117,17 @@
 
     static Intent createExecutionCompletedIntent(
             @NonNull Context context,
-            @NonNull String workSpecId,
+            @NonNull WorkGenerationalId id,
             boolean needsReschedule) {
-
         Intent intent = new Intent(context, SystemAlarmService.class);
         intent.setAction(ACTION_EXECUTION_COMPLETED);
-        intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
         intent.putExtra(KEY_NEEDS_RESCHEDULE, needsReschedule);
-        return intent;
+        return writeWorkGenerationalId(intent, id);
     }
 
     // members
     private final Context mContext;
-    private final Map<String, ExecutionListener> mPendingDelayMet;
+    private final Map<WorkGenerationalId, DelayMetCommandHandler> mPendingDelayMet;
     private final Object mLock;
     private final StartStopTokens mStartStopTokens;
 
@@ -121,13 +139,14 @@
     }
 
     @Override
-    public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
+    public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
         synchronized (mLock) {
             // This listener is only necessary for knowing when a pending work is complete.
             // Delegate to the underlying execution listener itself.
-            ExecutionListener listener = mPendingDelayMet.remove(workSpecId);
+            DelayMetCommandHandler listener = mPendingDelayMet.remove(id);
+            mStartStopTokens.remove(id);
             if (listener != null) {
-                listener.onExecuted(workSpecId, needsReschedule);
+                listener.onExecuted(needsReschedule);
             }
         }
     }
@@ -187,9 +206,8 @@
             int startId,
             @NonNull SystemAlarmDispatcher dispatcher) {
 
-        Bundle extras = intent.getExtras();
-        String workSpecId = extras.getString(KEY_WORKSPEC_ID);
-        Logger.get().debug(TAG, "Handling schedule work for " + workSpecId);
+        WorkGenerationalId id = readWorkGenerationalId(intent);
+        Logger.get().debug(TAG, "Handling schedule work for " + id);
 
         WorkManagerImpl workManager = dispatcher.getWorkManager();
         WorkDatabase workDatabase = workManager.getWorkDatabase();
@@ -197,7 +215,7 @@
 
         try {
             WorkSpecDao workSpecDao = workDatabase.workSpecDao();
-            WorkSpec workSpec = workSpecDao.getWorkSpec(workSpecId);
+            WorkSpec workSpec = workSpecDao.getWorkSpec(id.getWorkSpecId());
 
             // It is possible that this WorkSpec got cancelled/pruned since this isn't part of
             // the same database transaction as marking it enqueued (for example, if we using
@@ -208,7 +226,7 @@
             // See b/114705286.
             if (workSpec == null) {
                 Logger.get().warning(TAG,
-                        "Skipping scheduling " + workSpecId + " because it's no longer in "
+                        "Skipping scheduling " + id + " because it's no longer in "
                         + "the DB");
                 return;
             } else if (workSpec.state.isFinished()) {
@@ -216,7 +234,7 @@
                 // if the process gets killed, the Alarm is necessary to pick up the execution of
                 // Work.
                 Logger.get().warning(TAG,
-                        "Skipping scheduling " + workSpecId + "because it is finished.");
+                        "Skipping scheduling " + id + "because it is finished.");
                 return;
             }
 
@@ -226,16 +244,16 @@
 
             if (!workSpec.hasConstraints()) {
                 Logger.get().debug(TAG,
-                        "Setting up Alarms for " + workSpecId + "at " + triggerAt);
-                Alarms.setAlarm(mContext, dispatcher.getWorkManager(), workSpecId, triggerAt);
+                        "Setting up Alarms for " + id + "at " + triggerAt);
+                Alarms.setAlarm(mContext, dispatcher.getWorkManager(), id, triggerAt);
             } else {
                 // Schedule an alarm irrespective of whether all constraints matched.
                 Logger.get().debug(TAG,
-                        "Opportunistically setting an alarm for " + workSpecId + "at " + triggerAt);
+                        "Opportunistically setting an alarm for " + id + "at " + triggerAt);
                 Alarms.setAlarm(
                         mContext,
                         dispatcher.getWorkManager(),
-                        workSpecId,
+                        id,
                         triggerAt);
 
                 // Schedule an update for constraint proxies
@@ -259,21 +277,20 @@
             int startId,
             @NonNull SystemAlarmDispatcher dispatcher) {
 
-        Bundle extras = intent.getExtras();
         synchronized (mLock) {
-            String workSpecId = extras.getString(KEY_WORKSPEC_ID);
-            Logger.get().debug(TAG, "Handing delay met for " + workSpecId);
+            WorkGenerationalId id = readWorkGenerationalId(intent);
+            Logger.get().debug(TAG, "Handing delay met for " + id);
 
             // Check to see if we are already handling an ACTION_DELAY_MET for the WorkSpec.
             // If we are, then there is nothing for us to do.
-            if (!mPendingDelayMet.containsKey(workSpecId)) {
+            if (!mPendingDelayMet.containsKey(id)) {
                 DelayMetCommandHandler delayMetCommandHandler =
-                        new DelayMetCommandHandler(mContext, startId, workSpecId,
-                                dispatcher, mStartStopTokens);
-                mPendingDelayMet.put(workSpecId, delayMetCommandHandler);
+                        new DelayMetCommandHandler(mContext, startId,
+                                dispatcher, mStartStopTokens.tokenFor(id));
+                mPendingDelayMet.put(id, delayMetCommandHandler);
                 delayMetCommandHandler.handleProcessWork();
             } else {
-                Logger.get().debug(TAG, "WorkSpec " + workSpecId
+                Logger.get().debug(TAG, "WorkSpec " + id
                         + " is is already being handled for ACTION_DELAY_MET");
             }
         }
@@ -285,16 +302,26 @@
 
         Bundle extras = intent.getExtras();
         String workSpecId = extras.getString(KEY_WORKSPEC_ID);
-        Logger.get().debug(TAG, "Handing stopWork work for " + workSpecId);
-
-        StartStopToken runId = mStartStopTokens.remove(workSpecId);
-        if (runId != null) {
-            dispatcher.getWorkManager().stopWork(runId);
+        List<StartStopToken> tokens;
+        if (extras.containsKey(KEY_WORKSPEC_GENERATION)) {
+            int generation = extras.getInt(KEY_WORKSPEC_GENERATION);
+            tokens = new ArrayList<>(1);
+            StartStopToken id = mStartStopTokens.remove(
+                    new WorkGenerationalId(workSpecId, generation));
+            if (id != null) {
+                tokens.add(id);
+            }
+        } else {
+            tokens = mStartStopTokens.remove(workSpecId);
         }
-        Alarms.cancelAlarm(mContext, dispatcher.getWorkManager(), workSpecId);
+        for (StartStopToken token: tokens) {
+            Logger.get().debug(TAG, "Handing stopWork work for " + workSpecId);
+            dispatcher.getWorkManager().stopWork(token);
+            Alarms.cancelAlarm(mContext, dispatcher.getWorkManager(), token.getId());
 
-        // Notify dispatcher, so it can clean up.
-        dispatcher.onExecuted(workSpecId, false /* never reschedule */);
+            // Notify dispatcher, so it can clean up.
+            dispatcher.onExecuted(token.getId(), false /* never reschedule */);
+        }
     }
 
     private void handleConstraintsChanged(
@@ -321,15 +348,13 @@
     private void handleExecutionCompleted(
             @NonNull Intent intent,
             int startId) {
-
-        Bundle extras = intent.getExtras();
-        String workSpecId = extras.getString(KEY_WORKSPEC_ID);
-        boolean needsReschedule = extras.getBoolean(KEY_NEEDS_RESCHEDULE);
+        WorkGenerationalId id = readWorkGenerationalId(intent);
+        boolean needsReschedule = intent.getExtras().getBoolean(KEY_NEEDS_RESCHEDULE);
         Logger.get().debug(
                 TAG,
                 "Handling onExecutionCompleted " + intent + ", " + startId);
         // Delegate onExecuted() to the command handler.
-        onExecuted(workSpecId, needsReschedule);
+        onExecuted(id, needsReschedule);
     }
 
     @SuppressWarnings("deprecation")
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
index 03b2890..ea6f677237 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/ConstraintsCommandHandler.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl.background.systemalarm;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import android.content.Context;
 import android.content.Intent;
 
@@ -85,7 +87,7 @@
 
         for (WorkSpec workSpec : eligibleWorkSpecs) {
             String workSpecId = workSpec.id;
-            Intent intent = CommandHandler.createDelayMetIntent(mContext, workSpecId);
+            Intent intent = CommandHandler.createDelayMetIntent(mContext, generationalId(workSpec));
             Logger.get().debug(TAG, "Creating a delay_met command for workSpec with id (" + workSpecId + ")");
             mDispatcher.getTaskExecutor().getMainThreadExecutor().execute(
                     new SystemAlarmDispatcher.AddRunnable(mDispatcher, intent, mStartId));
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
index 1506cc7..6632c7a 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/DelayMetCommandHandler.java
@@ -17,6 +17,7 @@
 package androidx.work.impl.background.systemalarm;
 
 import static androidx.work.impl.background.systemalarm.CommandHandler.WORK_PROCESSING_TIME_IN_MS;
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
 
 import android.content.Context;
 import android.content.Intent;
@@ -27,11 +28,11 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.WorkerThread;
 import androidx.work.Logger;
-import androidx.work.impl.ExecutionListener;
-import androidx.work.impl.StartStopTokens;
+import androidx.work.impl.StartStopToken;
 import androidx.work.impl.constraints.WorkConstraintsCallback;
 import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
 import androidx.work.impl.constraints.trackers.Trackers;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.WakeLocks;
 import androidx.work.impl.utils.WorkTimer;
@@ -49,7 +50,6 @@
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public class DelayMetCommandHandler implements
         WorkConstraintsCallback,
-        ExecutionListener,
         WorkTimer.TimeLimitExceededListener {
 
     private static final String TAG = Logger.tagWithPrefix("DelayMetCommandHandler");
@@ -86,7 +86,7 @@
 
     private final Context mContext;
     private final int mStartId;
-    private final String mWorkSpecId;
+    private final WorkGenerationalId mWorkGenerationalId;
     private final SystemAlarmDispatcher mDispatcher;
     private final WorkConstraintsTrackerImpl mWorkConstraintsTracker;
     private final Object mLock;
@@ -97,19 +97,18 @@
 
     @Nullable private PowerManager.WakeLock mWakeLock;
     private boolean mHasConstraints;
-    private final StartStopTokens mStartStopTokens;
+    private final StartStopToken mToken;
 
     DelayMetCommandHandler(
             @NonNull Context context,
             int startId,
-            @NonNull String workSpecId,
             @NonNull SystemAlarmDispatcher dispatcher,
-            @NonNull StartStopTokens startStopTokens) {
+            @NonNull StartStopToken startStopToken) {
         mContext = context;
         mStartId = startId;
         mDispatcher = dispatcher;
-        mWorkSpecId = workSpecId;
-        mStartStopTokens = startStopTokens;
+        mWorkGenerationalId = startStopToken.getId();
+        mToken = startStopToken;
         Trackers trackers = dispatcher.getWorkManager().getTrackers();
         mSerialExecutor = dispatcher.getTaskExecutor().getSerialTaskExecutor();
         mMainThreadExecutor = dispatcher.getTaskExecutor().getMainThreadExecutor();
@@ -125,7 +124,7 @@
         // constraints are met. Ensure the workSpecId we are interested is part of the list
         // before we call Processor#startWork().
         for (WorkSpec spec: workSpecs) {
-            if (mWorkSpecId.equals(spec.id)) {
+            if (generationalId(spec).equals(mWorkGenerationalId)) {
                 mSerialExecutor.execute(this::startWork);
                 return;
             }
@@ -136,38 +135,35 @@
         if (mCurrentState == STATE_INITIAL) {
             mCurrentState = STATE_START_REQUESTED;
 
-            Logger.get().debug(TAG, "onAllConstraintsMet for " + mWorkSpecId);
+            Logger.get().debug(TAG, "onAllConstraintsMet for " + mWorkGenerationalId);
             // Constraints met, schedule execution
             // Not using WorkManagerImpl#startWork() here because we need to know if the
             // processor actually enqueued the work here.
-            boolean isEnqueued = mDispatcher.getProcessor().startWork(
-                    mStartStopTokens.tokenFor(mWorkSpecId)
-            );
+            boolean isEnqueued = mDispatcher.getProcessor().startWork(mToken);
 
             if (isEnqueued) {
                 // setup timers to enforce quotas on workers that have
                 // been enqueued
                 mDispatcher.getWorkTimer()
-                        .startTimer(mWorkSpecId, WORK_PROCESSING_TIME_IN_MS, this);
+                        .startTimer(mWorkGenerationalId, WORK_PROCESSING_TIME_IN_MS, this);
             } else {
                 // if we did not actually enqueue the work, it was enqueued before
                 // cleanUp and pretend this never happened.
                 cleanUp();
             }
         } else {
-            Logger.get().debug(TAG, "Already started work for " + mWorkSpecId);
+            Logger.get().debug(TAG, "Already started work for " + mWorkGenerationalId);
         }
     }
 
-    @Override
-    public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
-        Logger.get().debug(TAG, "onExecuted " + workSpecId + ", " + needsReschedule);
+    void onExecuted(boolean needsReschedule) {
+        Logger.get().debug(TAG, "onExecuted " + mWorkGenerationalId + ", " + needsReschedule);
         cleanUp();
-        mStartStopTokens.remove(workSpecId);
         if (needsReschedule) {
             // We need to reschedule the WorkSpec. WorkerWrapper may also call Scheduler.schedule()
             // but given that we will only consider WorkSpecs that are eligible that it safe.
-            Intent reschedule = CommandHandler.createScheduleWorkIntent(mContext, mWorkSpecId);
+            Intent reschedule = CommandHandler.createScheduleWorkIntent(mContext,
+                    mWorkGenerationalId);
             mMainThreadExecutor.execute(
                     new SystemAlarmDispatcher.AddRunnable(mDispatcher, reschedule, mStartId));
         }
@@ -183,8 +179,8 @@
     }
 
     @Override
-    public void onTimeLimitExceeded(@NonNull String workSpecId) {
-        Logger.get().debug(TAG, "Exceeded time limits on execution for " + workSpecId);
+    public void onTimeLimitExceeded(@NonNull WorkGenerationalId id) {
+        Logger.get().debug(TAG, "Exceeded time limits on execution for " + id);
         mSerialExecutor.execute(this::stopWork);
     }
 
@@ -195,16 +191,16 @@
 
     @WorkerThread
     void handleProcessWork() {
-        mWakeLock = WakeLocks.newWakeLock(mContext, mWorkSpecId + " (" + mStartId + ")");
+        String workSpecId = mWorkGenerationalId.getWorkSpecId();
+        mWakeLock = WakeLocks.newWakeLock(mContext, workSpecId + " (" + mStartId + ")");
         Logger.get().debug(TAG,
-                "Acquiring wakelock " + mWakeLock + "for WorkSpec " + mWorkSpecId);
+                "Acquiring wakelock " + mWakeLock + "for WorkSpec " + workSpecId);
         mWakeLock.acquire();
 
         WorkSpec workSpec = mDispatcher.getWorkManager()
                 .getWorkDatabase()
                 .workSpecDao()
-                .getWorkSpec(mWorkSpecId);
-
+                .getWorkSpec(workSpecId);
         // This should typically never happen. Cancelling work should remove alarms, but if an
         // alarm has already fired, then fire a stop work request to remove the pending delay met
         // command handler.
@@ -218,7 +214,7 @@
         mHasConstraints = workSpec.hasConstraints();
 
         if (!mHasConstraints) {
-            Logger.get().debug(TAG, "No constraints for " + mWorkSpecId);
+            Logger.get().debug(TAG, "No constraints for " + workSpecId);
             onAllConstraintsMet(Collections.singletonList(workSpec));
         } else {
             // Allow tracker to report constraint changes
@@ -230,32 +226,30 @@
         // No need to release the wake locks here. The stopWork command will eventually call
         // onExecuted() if there is a corresponding pending delay met command handler; which in
         // turn calls cleanUp().
-
+        String workSpecId = mWorkGenerationalId.getWorkSpecId();
         if (mCurrentState < STATE_STOP_REQUESTED) {
             mCurrentState = STATE_STOP_REQUESTED;
-            Logger.get().debug(
-                    TAG,
-                    "Stopping work for WorkSpec " + mWorkSpecId);
-            Intent stopWork = CommandHandler.createStopWorkIntent(mContext, mWorkSpecId);
+            Logger.get().debug(TAG, "Stopping work for WorkSpec " + workSpecId);
+            Intent stopWork = CommandHandler.createStopWorkIntent(mContext, mWorkGenerationalId);
             mMainThreadExecutor.execute(
                     new SystemAlarmDispatcher.AddRunnable(mDispatcher, stopWork, mStartId));
             // There are cases where the work may not have been enqueued at all, and therefore
             // the processor is completely unaware of such a workSpecId in which case a
             // reschedule should not happen. For e.g. DELAY_MET when constraints are not met,
             // should not result in a reschedule.
-            if (mDispatcher.getProcessor().isEnqueued(mWorkSpecId)) {
-                Logger.get().debug(TAG, "WorkSpec " + mWorkSpecId + " needs to be rescheduled");
+            if (mDispatcher.getProcessor().isEnqueued(mWorkGenerationalId.getWorkSpecId())) {
+                Logger.get().debug(TAG, "WorkSpec " + workSpecId + " needs to be rescheduled");
                 Intent reschedule = CommandHandler.createScheduleWorkIntent(mContext,
-                        mWorkSpecId);
+                        mWorkGenerationalId);
                 mMainThreadExecutor.execute(
-                        new SystemAlarmDispatcher.AddRunnable(mDispatcher, reschedule,
-                                mStartId));
+                        new SystemAlarmDispatcher.AddRunnable(mDispatcher, reschedule, mStartId)
+                );
             } else {
-                Logger.get().debug(TAG, "Processor does not have WorkSpec " + mWorkSpecId
+                Logger.get().debug(TAG, "Processor does not have WorkSpec " + workSpecId
                         + ". No need to reschedule");
             }
         } else {
-            Logger.get().debug(TAG, "Already stopped work for " + mWorkSpecId);
+            Logger.get().debug(TAG, "Already stopped work for " + workSpecId);
         }
     }
 
@@ -270,11 +264,12 @@
             // clean up constraint trackers
             mWorkConstraintsTracker.reset();
             // stop timers
-            mDispatcher.getWorkTimer().stopTimer(mWorkSpecId);
+            mDispatcher.getWorkTimer().stopTimer(mWorkGenerationalId);
 
             // release wake locks
             if (mWakeLock != null && mWakeLock.isHeld()) {
-                Logger.get().debug(TAG, "Releasing wakelock " + mWakeLock + "for WorkSpec " + mWorkSpecId);
+                Logger.get().debug(TAG, "Releasing wakelock " + mWakeLock
+                        + "for WorkSpec " + mWorkGenerationalId);
                 mWakeLock.release();
             }
         }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
index ed2d15e..cda9784 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcher.java
@@ -32,6 +32,7 @@
 import androidx.work.impl.Processor;
 import androidx.work.impl.StartStopTokens;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.utils.WakeLocks;
 import androidx.work.impl.utils.WorkTimer;
 import androidx.work.impl.utils.taskexecutor.SerialExecutor;
@@ -108,7 +109,7 @@
     }
 
     @Override
-    public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
+    public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
 
         // When there are lots of workers completing at around the same time,
         // this creates lock contention for the DelayMetCommandHandlers inside the CommandHandler.
@@ -119,7 +120,7 @@
                         this,
                         CommandHandler.createExecutionCompletedIntent(
                                 mContext,
-                                workSpecId,
+                                id,
                                 needsReschedule),
                         DEFAULT_START_ID));
     }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java
index 15a272a..49c6ac6 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemalarm/SystemAlarmScheduler.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl.background.systemalarm;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import android.content.Context;
 import android.content.Intent;
 
@@ -65,7 +67,8 @@
      */
     private void scheduleWorkSpec(@NonNull WorkSpec workSpec) {
         Logger.get().debug(TAG, "Scheduling work with workSpecId " + workSpec.id);
-        Intent scheduleIntent = CommandHandler.createScheduleWorkIntent(mContext, workSpec.id);
+        Intent scheduleIntent = CommandHandler.createScheduleWorkIntent(mContext,
+                generationalId(workSpec));
         mContext.startService(scheduleIntent);
     }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
index 5f0019c..4415f91 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobInfoConverter.java
@@ -51,6 +51,7 @@
 
     static final String EXTRA_WORK_SPEC_ID = "EXTRA_WORK_SPEC_ID";
     static final String EXTRA_IS_PERIODIC = "EXTRA_IS_PERIODIC";
+    static final String EXTRA_WORK_SPEC_GENERATION = "EXTRA_WORK_SPEC_GENERATION";
 
     private final ComponentName mWorkServiceComponent;
 
@@ -73,6 +74,7 @@
         Constraints constraints = workSpec.constraints;
         PersistableBundle extras = new PersistableBundle();
         extras.putString(EXTRA_WORK_SPEC_ID, workSpec.id);
+        extras.putInt(EXTRA_WORK_SPEC_GENERATION, workSpec.getGeneration());
         extras.putBoolean(EXTRA_IS_PERIODIC, workSpec.isPeriodic());
         JobInfo.Builder builder = new JobInfo.Builder(jobId, mWorkServiceComponent)
                 .setRequiresCharging(constraints.requiresCharging())
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
index 754be5f..1cdd29f 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobScheduler.java
@@ -18,8 +18,11 @@
 import static android.content.Context.JOB_SCHEDULER_SERVICE;
 
 import static androidx.work.OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST;
+import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_GENERATION;
 import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_ID;
+import static androidx.work.impl.model.SystemIdInfoKt.systemIdInfo;
 import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
 
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
@@ -27,7 +30,6 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.PersistableBundle;
-import android.text.TextUtils;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -40,6 +42,7 @@
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
 import androidx.work.impl.model.SystemIdInfo;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.IdGenerator;
@@ -75,10 +78,10 @@
 
     @VisibleForTesting
     public SystemJobScheduler(
-            Context context,
-            WorkManagerImpl workManager,
-            JobScheduler jobScheduler,
-            SystemJobInfoConverter systemJobInfoConverter) {
+            @NonNull Context context,
+            @NonNull WorkManagerImpl workManager,
+            @NonNull JobScheduler jobScheduler,
+            @NonNull SystemJobInfoConverter systemJobInfoConverter) {
         mContext = context;
         mWorkManager = workManager;
         mJobScheduler = jobScheduler;
@@ -115,16 +118,15 @@
                     workDatabase.setTransactionSuccessful();
                     continue;
                 }
-
-                SystemIdInfo info = workDatabase.systemIdInfoDao()
-                        .getSystemIdInfo(workSpec.id);
+                WorkGenerationalId generationalId = generationalId(workSpec);
+                SystemIdInfo info = workDatabase.systemIdInfoDao().getSystemIdInfo(generationalId);
 
                 int jobId = info != null ? info.systemId : idGenerator.nextJobSchedulerIdWithRange(
                         mWorkManager.getConfiguration().getMinJobSchedulerId(),
                         mWorkManager.getConfiguration().getMaxJobSchedulerId());
 
                 if (info == null) {
-                    SystemIdInfo newSystemIdInfo = new SystemIdInfo(workSpec.id, jobId);
+                    SystemIdInfo newSystemIdInfo = systemIdInfo(generationalId, jobId);
                     mWorkManager.getWorkDatabase()
                             .systemIdInfoDao()
                             .insertSystemIdInfo(newSystemIdInfo);
@@ -178,7 +180,7 @@
      * @param workSpec The {@link WorkSpec} to schedule with JobScheduler.
      */
     @VisibleForTesting
-    public void scheduleInternal(WorkSpec workSpec, int jobId) {
+    public void scheduleInternal(@NonNull WorkSpec workSpec, int jobId) {
         JobInfo jobInfo = mSystemJobInfoConverter.convert(workSpec, jobId);
         Logger.get().debug(
                 TAG,
@@ -186,8 +188,7 @@
         try {
             int result = mJobScheduler.schedule(jobInfo);
             if (result == JobScheduler.RESULT_FAILURE) {
-                Logger.get()
-                        .warning(TAG, "Unable to schedule work ID " + workSpec.id);
+                Logger.get().warning(TAG, "Unable to schedule work ID " + workSpec.id);
                 if (workSpec.expedited
                         && workSpec.outOfQuotaPolicy == RUN_AS_NON_EXPEDITED_WORK_REQUEST) {
                     // Falling back to a non-expedited job.
@@ -301,9 +302,9 @@
         Set<String> jobSchedulerWorkSpecs = new HashSet<>(jobSize);
         if (jobs != null && !jobs.isEmpty()) {
             for (JobInfo jobInfo : jobs) {
-                String workSpecId = getWorkSpecIdFromJobInfo(jobInfo);
-                if (!TextUtils.isEmpty(workSpecId)) {
-                    jobSchedulerWorkSpecs.add(workSpecId);
+                WorkGenerationalId id = getWorkGenerationalIdFromJobInfo(jobInfo);
+                if (id != null) {
+                    jobSchedulerWorkSpecs.add(id.getWorkSpecId());
                 } else {
                     // Cancels invalid jobs owned by WorkManager.
                     // These jobs are invalid (in-actionable on our part) but occupy slots in
@@ -395,7 +396,8 @@
         List<Integer> jobIds = new ArrayList<>(2);
 
         for (JobInfo jobInfo : jobs) {
-            if (workSpecId.equals(getWorkSpecIdFromJobInfo(jobInfo))) {
+            WorkGenerationalId id = getWorkGenerationalIdFromJobInfo(jobInfo);
+            if (id != null && workSpecId.equals(id.getWorkSpecId())) {
                 jobIds.add(jobInfo.getId());
             }
         }
@@ -403,12 +405,13 @@
         return jobIds;
     }
 
-    @SuppressWarnings("ConstantConditions")
-    private static @Nullable String getWorkSpecIdFromJobInfo(@NonNull JobInfo jobInfo) {
+    @Nullable
+    private static WorkGenerationalId getWorkGenerationalIdFromJobInfo(@NonNull JobInfo jobInfo) {
         PersistableBundle extras = jobInfo.getExtras();
         try {
             if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
-                return extras.getString(EXTRA_WORK_SPEC_ID);
+                int generation = extras.getInt(EXTRA_WORK_SPEC_GENERATION, 0);
+                return new WorkGenerationalId(extras.getString(EXTRA_WORK_SPEC_ID), generation);
             }
         } catch (NullPointerException e) {
             // b/138364061: BaseBundle.mMap seems to be null in some cases here.  Ignore and return
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
index c92da864..03f6342 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/background/systemjob/SystemJobService.java
@@ -16,6 +16,7 @@
 
 package androidx.work.impl.background.systemjob;
 
+import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_GENERATION;
 import static androidx.work.impl.background.systemjob.SystemJobInfoConverter.EXTRA_WORK_SPEC_ID;
 
 import android.app.Application;
@@ -26,7 +27,6 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.PersistableBundle;
-import android.text.TextUtils;
 
 import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
@@ -39,6 +39,7 @@
 import androidx.work.impl.StartStopToken;
 import androidx.work.impl.StartStopTokens;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.model.WorkGenerationalId;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -54,7 +55,7 @@
 public class SystemJobService extends JobService implements ExecutionListener {
     private static final String TAG = Logger.tagWithPrefix("SystemJobService");
     private WorkManagerImpl mWorkManagerImpl;
-    private final Map<String, JobParameters> mJobParameters = new HashMap<>();
+    private final Map<WorkGenerationalId, JobParameters> mJobParameters = new HashMap<>();
     private final StartStopTokens mStartStopTokens = new StartStopTokens();
 
     @Override
@@ -103,17 +104,18 @@
             return false;
         }
 
-        String workSpecId = getWorkSpecIdFromJobParameters(params);
-        if (TextUtils.isEmpty(workSpecId)) {
+        WorkGenerationalId workGenerationalId = workGenerationalIdFromJobParameters(params);
+        if (workGenerationalId == null) {
             Logger.get().error(TAG, "WorkSpec id not found!");
             return false;
         }
 
         synchronized (mJobParameters) {
-            if (mJobParameters.containsKey(workSpecId)) {
+            if (mJobParameters.containsKey(workGenerationalId)) {
                 // This condition may happen due to our workaround for an undesired behavior in API
                 // 23.  See the documentation in {@link SystemJobScheduler#schedule}.
-                Logger.get().debug(TAG, "Job is already being executed by SystemJobService: " + workSpecId);
+                Logger.get().debug(TAG, "Job is already being executed by SystemJobService: "
+                        + workGenerationalId);
                 return false;
             }
 
@@ -121,8 +123,8 @@
             // returns true. This is because JobScheduler ensures that for PeriodicWork, constraints
             // are actually met irrespective.
 
-            Logger.get().debug(TAG, "onStartJob for " + workSpecId);
-            mJobParameters.put(workSpecId, params);
+            Logger.get().debug(TAG, "onStartJob for " + workGenerationalId);
+            mJobParameters.put(workGenerationalId, params);
         }
 
         WorkerParameters.RuntimeExtras runtimeExtras = null;
@@ -148,7 +150,7 @@
         // In such cases, we rely on SystemJobService to ask for a reschedule by calling
         // jobFinished(params, true) in onExecuted(...);
         // For more information look at b/123211993
-        mWorkManagerImpl.startWork(mStartStopTokens.tokenFor(workSpecId), runtimeExtras);
+        mWorkManagerImpl.startWork(mStartStopTokens.tokenFor(workGenerationalId), runtimeExtras);
         return true;
     }
 
@@ -159,32 +161,32 @@
             return true;
         }
 
-        String workSpecId = getWorkSpecIdFromJobParameters(params);
-        if (TextUtils.isEmpty(workSpecId)) {
+        WorkGenerationalId workGenerationalId = workGenerationalIdFromJobParameters(params);
+        if (workGenerationalId == null) {
             Logger.get().error(TAG, "WorkSpec id not found!");
             return false;
         }
 
-        Logger.get().debug(TAG, "onStopJob for " + workSpecId);
+        Logger.get().debug(TAG, "onStopJob for " + workGenerationalId);
 
         synchronized (mJobParameters) {
-            mJobParameters.remove(workSpecId);
+            mJobParameters.remove(workGenerationalId);
         }
-        StartStopToken runId = mStartStopTokens.remove(workSpecId);
+        StartStopToken runId = mStartStopTokens.remove(workGenerationalId);
         if (runId != null) {
             mWorkManagerImpl.stopWork(runId);
         }
-        return !mWorkManagerImpl.getProcessor().isCancelled(workSpecId);
+        return !mWorkManagerImpl.getProcessor().isCancelled(workGenerationalId.getWorkSpecId());
     }
 
     @Override
-    public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
-        Logger.get().debug(TAG, workSpecId + " executed on JobScheduler");
+    public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
+        Logger.get().debug(TAG, id.getWorkSpecId() + " executed on JobScheduler");
         JobParameters parameters;
         synchronized (mJobParameters) {
-            parameters = mJobParameters.remove(workSpecId);
+            parameters = mJobParameters.remove(id);
         }
-        mStartStopTokens.remove(workSpecId);
+        mStartStopTokens.remove(id);
         if (parameters != null) {
             jobFinished(parameters, needsReschedule);
         }
@@ -192,11 +194,14 @@
 
     @Nullable
     @SuppressWarnings("ConstantConditions")
-    private static String getWorkSpecIdFromJobParameters(@NonNull JobParameters parameters) {
+    private static WorkGenerationalId workGenerationalIdFromJobParameters(
+            @NonNull JobParameters parameters
+    ) {
         try {
             PersistableBundle extras = parameters.getExtras();
             if (extras != null && extras.containsKey(EXTRA_WORK_SPEC_ID)) {
-                return extras.getString(EXTRA_WORK_SPEC_ID);
+                return new WorkGenerationalId(extras.getString(EXTRA_WORK_SPEC_ID),
+                        extras.getInt(EXTRA_WORK_SPEC_GENERATION));
             }
         } catch (NullPointerException e) {
             // b/138441699: BaseBundle.getString sometimes throws an NPE.  Ignore and return null.
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java
index 1ef298b..e556d39 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/foreground/SystemForegroundDispatcher.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE;
 
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
+
 import android.app.Notification;
 import android.content.Context;
 import android.content.Intent;
@@ -38,6 +40,7 @@
 import androidx.work.impl.constraints.WorkConstraintsCallback;
 import androidx.work.impl.constraints.WorkConstraintsTracker;
 import androidx.work.impl.constraints.WorkConstraintsTrackerImpl;
+import androidx.work.impl.model.WorkGenerationalId;
 import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
 
@@ -67,6 +70,7 @@
     private static final String KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID";
     private static final String KEY_FOREGROUND_SERVICE_TYPE = "KEY_FOREGROUND_SERVICE_TYPE";
     private static final String KEY_WORKSPEC_ID = "KEY_WORKSPEC_ID";
+    private static final String KEY_GENERATION = "KEY_GENERATION";
 
     // actions
     private static final String ACTION_START_FOREGROUND = "ACTION_START_FOREGROUND";
@@ -83,13 +87,13 @@
     final Object mLock;
 
     @SuppressWarnings("WeakerAccess") // Synthetic access
-    String mCurrentForegroundWorkSpecId;
+    WorkGenerationalId mCurrentForegroundId;
 
     @SuppressWarnings("WeakerAccess") // Synthetic access
-    final Map<String, ForegroundInfo> mForegroundInfoById;
+    final Map<WorkGenerationalId, ForegroundInfo> mForegroundInfoById;
 
     @SuppressWarnings("WeakerAccess") // Synthetic access
-    final Map<String, WorkSpec> mWorkSpecById;
+    final Map<WorkGenerationalId, WorkSpec> mWorkSpecById;
 
     @SuppressWarnings("WeakerAccess") // Synthetic access
     final Set<WorkSpec> mTrackedWorkSpecs;
@@ -105,7 +109,7 @@
         mLock = new Object();
         mWorkManagerImpl = WorkManagerImpl.getInstance(mContext);
         mTaskExecutor = mWorkManagerImpl.getWorkTaskExecutor();
-        mCurrentForegroundWorkSpecId = null;
+        mCurrentForegroundId = null;
         mForegroundInfoById = new LinkedHashMap<>();
         mTrackedWorkSpecs = new HashSet<>();
         mWorkSpecById = new HashMap<>();
@@ -123,7 +127,7 @@
         mLock = new Object();
         mWorkManagerImpl = workManagerImpl;
         mTaskExecutor = mWorkManagerImpl.getWorkTaskExecutor();
-        mCurrentForegroundWorkSpecId = null;
+        mCurrentForegroundId = null;
         mForegroundInfoById = new LinkedHashMap<>();
         mTrackedWorkSpecs = new HashSet<>();
         mWorkSpecById = new HashMap<>();
@@ -133,10 +137,10 @@
 
     @MainThread
     @Override
-    public void onExecuted(@NonNull String workSpecId, boolean needsReschedule) {
+    public void onExecuted(@NonNull WorkGenerationalId id, boolean needsReschedule) {
         boolean removed = false;
         synchronized (mLock) {
-            WorkSpec workSpec = mWorkSpecById.remove(workSpecId);
+            WorkSpec workSpec = mWorkSpecById.remove(id);
             if (workSpec != null) {
                 removed = mTrackedWorkSpecs.remove(workSpec);
             }
@@ -146,23 +150,23 @@
             }
         }
 
-        ForegroundInfo removedInfo = mForegroundInfoById.remove(workSpecId);
+        ForegroundInfo removedInfo = mForegroundInfoById.remove(id);
         // Promote new notifications to the foreground if necessary.
-        if (workSpecId.equals(mCurrentForegroundWorkSpecId)) {
+        if (id.equals(mCurrentForegroundId)) {
             if (mForegroundInfoById.size() > 0) {
                 // Find the next eligible ForegroundInfo
                 // LinkedHashMap uses insertion order, so find the last one because that was
                 // the most recent ForegroundInfo used. That way when different WorkSpecs share
                 // notification ids, we still end up in a reasonably good place.
-                Iterator<Map.Entry<String, ForegroundInfo>> iterator =
+                Iterator<Map.Entry<WorkGenerationalId, ForegroundInfo>> iterator =
                         mForegroundInfoById.entrySet().iterator();
 
-                Map.Entry<String, ForegroundInfo> entry = iterator.next();
+                Map.Entry<WorkGenerationalId, ForegroundInfo> entry = iterator.next();
                 while (iterator.hasNext()) {
                     entry = iterator.next();
                 }
 
-                mCurrentForegroundWorkSpecId = entry.getKey();
+                mCurrentForegroundId = entry.getKey();
                 if (mCallback != null) {
                     ForegroundInfo info = entry.getValue();
                     mCallback.startForeground(
@@ -190,9 +194,9 @@
             // thread, so there is a chance that handleStop() fires before onExecuted() is called
             // on the main thread.
             Logger.get().debug(TAG,
-                    "Removing Notification (id: " + removedInfo.getNotificationId() +
-                            ", workSpecId: " + workSpecId +
-                            ", notificationType: " + removedInfo.getForegroundServiceType());
+                    "Removing Notification (id: " + removedInfo.getNotificationId()
+                            + ", workSpecId: " + id
+                            + ", notificationType: " + removedInfo.getForegroundServiceType());
             callback.cancelNotification(removedInfo.getNotificationId());
         }
     }
@@ -244,7 +248,7 @@
                 // (constraints are immutable)
                 if (workSpec != null && workSpec.hasConstraints()) {
                     synchronized (mLock) {
-                        mWorkSpecById.put(workSpecId, workSpec);
+                        mWorkSpecById.put(generationalId(workSpec), workSpec);
                         mTrackedWorkSpecs.add(workSpec);
                         mConstraintsTracker.replace(mTrackedWorkSpecs);
                     }
@@ -259,6 +263,8 @@
         int notificationId = intent.getIntExtra(KEY_NOTIFICATION_ID, 0);
         int notificationType = intent.getIntExtra(KEY_FOREGROUND_SERVICE_TYPE, 0);
         String workSpecId = intent.getStringExtra(KEY_WORKSPEC_ID);
+        int generation = intent.getIntExtra(KEY_GENERATION, 0);
+        WorkGenerationalId workId = new WorkGenerationalId(workSpecId, generation);
         Notification notification = intent.getParcelableExtra(KEY_NOTIFICATION);
 
         Logger.get().debug(TAG,
@@ -271,10 +277,10 @@
             ForegroundInfo info = new ForegroundInfo(
                     notificationId, notification, notificationType);
 
-            mForegroundInfoById.put(workSpecId, info);
-            if (TextUtils.isEmpty(mCurrentForegroundWorkSpecId)) {
+            mForegroundInfoById.put(workId, info);
+            if (mCurrentForegroundId == null) {
                 // This is the current workSpecId which owns the Foreground lifecycle.
-                mCurrentForegroundWorkSpecId = workSpecId;
+                mCurrentForegroundId = workId;
                 mCallback.startForeground(notificationId, notificationType, notification);
             } else {
                 // Update notification
@@ -284,12 +290,13 @@
                 if (notificationType != FOREGROUND_SERVICE_TYPE_NONE
                         && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                     int foregroundServiceType = FOREGROUND_SERVICE_TYPE_NONE;
-                    for (Map.Entry<String, ForegroundInfo> entry : mForegroundInfoById.entrySet()) {
+                    for (Map.Entry<WorkGenerationalId, ForegroundInfo> entry
+                            : mForegroundInfoById.entrySet()) {
                         ForegroundInfo foregroundInfo = entry.getValue();
                         foregroundServiceType |= foregroundInfo.getForegroundServiceType();
                     }
                     ForegroundInfo currentInfo =
-                            mForegroundInfoById.get(mCurrentForegroundWorkSpecId);
+                            mForegroundInfoById.get(mCurrentForegroundId);
                     if (currentInfo != null) {
                         mCallback.startForeground(
                                 currentInfo.getNotificationId(),
@@ -331,7 +338,7 @@
                 String workSpecId = workSpec.id;
                 Logger.get().debug(TAG,
                         "Constraints unmet for WorkSpec " + workSpecId);
-                mWorkManagerImpl.stopForegroundWork(workSpecId);
+                mWorkManagerImpl.stopForegroundWork(generationalId(workSpec));
             }
         }
     }
@@ -347,15 +354,15 @@
     @NonNull
     public static Intent createStartForegroundIntent(
             @NonNull Context context,
-            @NonNull String workSpecId,
+            @NonNull WorkGenerationalId id,
             @NonNull ForegroundInfo info) {
         Intent intent = new Intent(context, SystemForegroundService.class);
         intent.setAction(ACTION_START_FOREGROUND);
-        intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
+        intent.putExtra(KEY_WORKSPEC_ID, id.getWorkSpecId());
+        intent.putExtra(KEY_GENERATION, id.getGeneration());
         intent.putExtra(KEY_NOTIFICATION_ID, info.getNotificationId());
         intent.putExtra(KEY_FOREGROUND_SERVICE_TYPE, info.getForegroundServiceType());
         intent.putExtra(KEY_NOTIFICATION, info.getNotification());
-        intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
         return intent;
     }
 
@@ -384,21 +391,22 @@
      * {@link SystemForegroundService}.
      *
      * @param context    The application {@link Context}
-     * @param workSpecId The {@link WorkSpec} id
+     * @param id The {@link WorkSpec} id
      * @param info       The {@link ForegroundInfo}
      * @return The {@link Intent}
      */
     @NonNull
     public static Intent createNotifyIntent(
             @NonNull Context context,
-            @NonNull String workSpecId,
+            @NonNull WorkGenerationalId id,
             @NonNull ForegroundInfo info) {
         Intent intent = new Intent(context, SystemForegroundService.class);
         intent.setAction(ACTION_NOTIFY);
         intent.putExtra(KEY_NOTIFICATION_ID, info.getNotificationId());
         intent.putExtra(KEY_FOREGROUND_SERVICE_TYPE, info.getForegroundServiceType());
         intent.putExtra(KEY_NOTIFICATION, info.getNotification());
-        intent.putExtra(KEY_WORKSPEC_ID, workSpecId);
+        intent.putExtra(KEY_WORKSPEC_ID, id.getWorkSpecId());
+        intent.putExtra(KEY_GENERATION, id.getGeneration());
         return intent;
     }
 
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfo.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfo.kt
index f1eb7d0..143e79b 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfo.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfo.kt
@@ -19,7 +19,6 @@
 import androidx.room.ColumnInfo
 import androidx.room.Entity
 import androidx.room.ForeignKey
-import androidx.room.PrimaryKey
 
 /**
  * Stores system ids for a [WorkSpec] id.
@@ -33,15 +32,22 @@
         childColumns = ["work_spec_id"],
         onDelete = ForeignKey.CASCADE,
         onUpdate = ForeignKey.CASCADE
-    )]
+    )],
+    primaryKeys = ["work_spec_id", "generation"]
 )
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 data class SystemIdInfo(
     @JvmField
     @ColumnInfo(name = "work_spec_id")
-    @PrimaryKey
     val workSpecId: String,
+
+    @ColumnInfo(defaultValue = "0")
+    val generation: Int,
+
     @JvmField
     @ColumnInfo(name = "system_id")
     val systemId: Int
-)
\ No newline at end of file
+)
+
+fun systemIdInfo(generationalId: WorkGenerationalId, systemId: Int) =
+    SystemIdInfo(generationalId.workSpecId, generationalId.generation, systemId)
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfoDao.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfoDao.kt
index 6bd049e..47b9e55 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfoDao.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/SystemIdInfoDao.kt
@@ -37,8 +37,18 @@
      * @param workSpecId The [WorkSpec] identifier.
      * @return The instance of [SystemIdInfo] if exists.
      */
-    @Query("SELECT * FROM SystemIdInfo WHERE work_spec_id=:workSpecId")
-    fun getSystemIdInfo(workSpecId: String): SystemIdInfo?
+    @Query("SELECT * FROM SystemIdInfo WHERE work_spec_id=:workSpecId AND generation=:generation")
+    fun getSystemIdInfo(workSpecId: String, generation: Int): SystemIdInfo?
+
+    fun getSystemIdInfo(id: WorkGenerationalId) = getSystemIdInfo(id.workSpecId, id.generation)
+
+    /**
+     * Removes [SystemIdInfo] corresponding to the [WorkSpec] identifier.
+     *
+     * @param workSpecId The [WorkSpec] identifier.
+     */
+    @Query("DELETE FROM SystemIdInfo where work_spec_id=:workSpecId AND generation=:generation")
+    fun removeSystemIdInfo(workSpecId: String, generation: Int)
 
     /**
      * Removes [SystemIdInfo] corresponding to the [WorkSpec] identifier.
@@ -48,6 +58,9 @@
     @Query("DELETE FROM SystemIdInfo where work_spec_id=:workSpecId")
     fun removeSystemIdInfo(workSpecId: String)
 
+    fun removeSystemIdInfo(id: WorkGenerationalId) =
+        removeSystemIdInfo(id.workSpecId, id.generation)
+
     /**
      * @return The [List] of [WorkSpec] ids.
      */
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt
index fc6dbf2..1869db8 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpec.kt
@@ -143,6 +143,9 @@
      */
     @ColumnInfo(name = "period_count", defaultValue = "0")
     var periodCount: Int = 0,
+
+    @ColumnInfo(defaultValue = "0")
+    val generation: Int = 0,
 ) {
     constructor(
         id: String,
@@ -393,4 +396,8 @@
             input?.map { it.toWorkInfo() }
         }
     }
-}
\ No newline at end of file
+}
+
+data class WorkGenerationalId(val workSpecId: String, val generation: Int)
+
+fun WorkSpec.generationalId() = WorkGenerationalId(id, generation)
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
index 4eb8b03..8ecd4ba 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
@@ -378,4 +378,7 @@
             "        (SELECT id FROM workspec WHERE state IN " + COMPLETED_STATES + "))"
     )
     fun pruneFinishedWorkWithZeroDependentsIgnoringKeepForAtLeast()
+
+    @Query("UPDATE workspec SET generation=generation+1 WHERE id=:id")
+    fun incrementGeneration(id: String)
 }
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
index 67469ec..c679e96 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/StopWorkRunnable.java
@@ -32,15 +32,15 @@
     private static final String TAG = Logger.tagWithPrefix("StopWorkRunnable");
 
     private final WorkManagerImpl mWorkManagerImpl;
-    private final StartStopToken mWorkSpecId;
+    private final StartStopToken mToken;
     private final boolean mStopInForeground;
 
     public StopWorkRunnable(
             @NonNull WorkManagerImpl workManagerImpl,
-            @NonNull StartStopToken workSpecId,
+            @NonNull StartStopToken startStopToken,
             boolean stopInForeground) {
         mWorkManagerImpl = workManagerImpl;
-        mWorkSpecId = workSpecId;
+        mToken = startStopToken;
         mStopInForeground = stopInForeground;
     }
     @Override
@@ -49,19 +49,19 @@
         if (mStopInForeground) {
             isStopped = mWorkManagerImpl
                     .getProcessor()
-                    .stopForegroundWork(mWorkSpecId.getWorkSpecId());
+                    .stopForegroundWork(mToken);
         } else {
             // This call is safe to make for foreground work because Processor ignores requests
             // to stop for foreground work.
             isStopped = mWorkManagerImpl
                     .getProcessor()
-                    .stopWork(mWorkSpecId);
+                    .stopWork(mToken);
         }
 
         Logger.get().debug(
                 TAG,
-                "StopWorkRunnable for " + mWorkSpecId.getWorkSpecId() + "; Processor.stopWork = "
-                        + isStopped);
+                "StopWorkRunnable for " + mToken.getId().getWorkSpecId() + "; Processor"
+                + ".stopWork" + " = " + isStopped);
 
     }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForegroundUpdater.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForegroundUpdater.java
index 876f079..1a36b81 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForegroundUpdater.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkForegroundUpdater.java
@@ -17,6 +17,7 @@
 package androidx.work.impl.utils;
 
 import static androidx.work.impl.foreground.SystemForegroundDispatcher.createNotifyIntent;
+import static androidx.work.impl.model.WorkSpecKt.generationalId;
 
 import android.content.Context;
 import android.content.Intent;
@@ -26,9 +27,9 @@
 import androidx.work.ForegroundInfo;
 import androidx.work.ForegroundUpdater;
 import androidx.work.Logger;
-import androidx.work.WorkInfo;
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.foreground.ForegroundProcessor;
+import androidx.work.impl.model.WorkSpec;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.futures.SettableFuture;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
@@ -84,8 +85,8 @@
                 try {
                     if (!future.isCancelled()) {
                         String workSpecId = id.toString();
-                        WorkInfo.State state = mWorkSpecDao.getState(workSpecId);
-                        if (state == null || state.isFinished()) {
+                        WorkSpec workSpec = mWorkSpecDao.getWorkSpec(workSpecId);
+                        if (workSpec == null || workSpec.state.isFinished()) {
                             // state == null would mean that the WorkSpec was replaced.
                             String message =
                                     "Calls to setForegroundAsync() must complete before a "
@@ -93,12 +94,14 @@
                                             + "returning an instance of Result.";
                             throw new IllegalStateException(message);
                         }
-
                         // startForeground() is idempotent
                         // NOTE: This will fail when the process is subject to foreground service
                         // restrictions. Propagate the exception to the caller.
                         mForegroundProcessor.startForeground(workSpecId, foregroundInfo);
-                        Intent intent = createNotifyIntent(context, workSpecId, foregroundInfo);
+                        // it is safe to take generation from this workspec, because only
+                        // one generation of the same work can run at a time.
+                        Intent intent = createNotifyIntent(context, generationalId(workSpec),
+                                foregroundInfo);
                         context.startService(intent);
                     }
                     future.set(null);
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkTimer.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkTimer.java
index 976956f..5189130 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkTimer.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/WorkTimer.java
@@ -22,6 +22,7 @@
 import androidx.work.Logger;
 import androidx.work.RunnableScheduler;
 import androidx.work.WorkRequest;
+import androidx.work.impl.model.WorkGenerationalId;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -39,8 +40,8 @@
     private static final String TAG = Logger.tagWithPrefix("WorkTimer");
 
     final RunnableScheduler mRunnableScheduler;
-    final Map<String, WorkTimerRunnable> mTimerMap;
-    final Map<String, TimeLimitExceededListener> mListeners;
+    final Map<WorkGenerationalId, WorkTimerRunnable> mTimerMap;
+    final Map<WorkGenerationalId, TimeLimitExceededListener> mListeners;
     final Object mLock;
 
     public WorkTimer(@NonNull RunnableScheduler scheduler) {
@@ -55,23 +56,23 @@
      * The {@link TimeLimitExceededListener} is notified when the execution time exceeds {@code
      * processingTimeMillis}.
      *
-     * @param workSpecId           The {@link androidx.work.impl.model.WorkSpec} id
+     * @param id           The {@link androidx.work.impl.model.WorkSpec} id
      * @param processingTimeMillis The allocated time for execution in milliseconds
      * @param listener             The listener which is notified when the execution time exceeds
      *                             {@code processingTimeMillis}
      */
     @SuppressWarnings("FutureReturnValueIgnored")
-    public void startTimer(@NonNull final String workSpecId,
+    public void startTimer(@NonNull final WorkGenerationalId id,
             long processingTimeMillis,
             @NonNull TimeLimitExceededListener listener) {
 
         synchronized (mLock) {
-            Logger.get().debug(TAG, "Starting timer for " + workSpecId);
+            Logger.get().debug(TAG, "Starting timer for " + id);
             // clear existing timer's first
-            stopTimer(workSpecId);
-            WorkTimerRunnable runnable = new WorkTimerRunnable(this, workSpecId);
-            mTimerMap.put(workSpecId, runnable);
-            mListeners.put(workSpecId, listener);
+            stopTimer(id);
+            WorkTimerRunnable runnable = new WorkTimerRunnable(this, id);
+            mTimerMap.put(id, runnable);
+            mListeners.put(id, listener);
             mRunnableScheduler.scheduleWithDelay(processingTimeMillis, runnable);
         }
     }
@@ -79,27 +80,27 @@
     /**
      * Stops tracking the execution time for a given {@link androidx.work.impl.model.WorkSpec}.
      *
-     * @param workSpecId The {@link androidx.work.impl.model.WorkSpec} id
+     * @param id The {@link androidx.work.impl.model.WorkSpec} id
      */
-    public void stopTimer(@NonNull final String workSpecId) {
+    public void stopTimer(@NonNull final WorkGenerationalId id) {
         synchronized (mLock) {
-            WorkTimerRunnable removed = mTimerMap.remove(workSpecId);
+            WorkTimerRunnable removed = mTimerMap.remove(id);
             if (removed != null) {
-                Logger.get().debug(TAG, "Stopping timer for " + workSpecId);
-                mListeners.remove(workSpecId);
+                Logger.get().debug(TAG, "Stopping timer for " + id);
+                mListeners.remove(id);
             }
         }
     }
 
     @VisibleForTesting
     @NonNull
-    public synchronized Map<String, WorkTimerRunnable> getTimerMap() {
+    public synchronized Map<WorkGenerationalId, WorkTimerRunnable> getTimerMap() {
         return mTimerMap;
     }
 
     @VisibleForTesting
     @NonNull
-    public synchronized Map<String, TimeLimitExceededListener> getListeners() {
+    public synchronized Map<WorkGenerationalId, TimeLimitExceededListener> getListeners() {
         return mListeners;
     }
 
@@ -113,26 +114,27 @@
         static final String TAG = "WrkTimerRunnable";
 
         private final WorkTimer mWorkTimer;
-        private final String mWorkSpecId;
+        private final WorkGenerationalId mWorkGenerationalId;
 
-        WorkTimerRunnable(@NonNull WorkTimer workTimer, @NonNull String workSpecId) {
+        WorkTimerRunnable(@NonNull WorkTimer workTimer, @NonNull WorkGenerationalId id) {
             mWorkTimer = workTimer;
-            mWorkSpecId = workSpecId;
+            mWorkGenerationalId = id;
         }
 
         @Override
         public void run() {
             synchronized (mWorkTimer.mLock) {
-                WorkTimerRunnable removed = mWorkTimer.mTimerMap.remove(mWorkSpecId);
+                WorkTimerRunnable removed = mWorkTimer.mTimerMap.remove(mWorkGenerationalId);
                 if (removed != null) {
                     // notify time limit exceeded.
-                    TimeLimitExceededListener listener = mWorkTimer.mListeners.remove(mWorkSpecId);
+                    TimeLimitExceededListener listener = mWorkTimer.mListeners
+                            .remove(mWorkGenerationalId);
                     if (listener != null) {
-                        listener.onTimeLimitExceeded(mWorkSpecId);
+                        listener.onTimeLimitExceeded(mWorkGenerationalId);
                     }
                 } else {
                     Logger.get().debug(TAG, String.format(
-                            "Timer with %s is already marked as complete.", mWorkSpecId));
+                            "Timer with %s is already marked as complete.", mWorkGenerationalId));
                 }
             }
         }
@@ -146,9 +148,9 @@
         /**
          * The time limit exceeded listener.
          *
-         * @param workSpecId The {@link androidx.work.impl.model.WorkSpec} id for which time limit
+         * @param id The {@link androidx.work.impl.model.WorkSpec} id for which time limit
          *                   has exceeded.
          */
-        void onTimeLimitExceeded(@NonNull String workSpecId);
+        void onTimeLimitExceeded(@NonNull WorkGenerationalId id);
     }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
index 7755b70..2781cdf 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/workers/DiagnosticsWorker.kt
@@ -22,6 +22,7 @@
 import androidx.work.WorkerParameters
 import androidx.work.impl.Scheduler
 import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.model.generationalId
 import androidx.work.impl.model.SystemIdInfoDao
 import androidx.work.impl.model.WorkNameDao
 import androidx.work.impl.model.WorkSpec
@@ -73,7 +74,7 @@
     val header = "\n Id \t Class Name\t ${systemIdHeader}\t State\t Unique Name\t Tags\t"
     append(header)
     workSpecs.forEach { workSpec ->
-        val systemId = systemIdInfoDao.getSystemIdInfo(workSpec.id)?.systemId
+        val systemId = systemIdInfoDao.getSystemIdInfo(workSpec.generationalId())?.systemId
         val names = workNameDao.getNamesForWorkSpecId(workSpec.id).joinToString(",")
         val tags = workTagDao.getTagsForWorkSpecId(workSpec.id).joinToString(",")
         append(workSpecRow(workSpec, names, systemId, tags))
diff --git a/work/work-runtime/src/schemas/androidx.work.impl.WorkDatabase/16.json b/work/work-runtime/src/schemas/androidx.work.impl.WorkDatabase/16.json
new file mode 100644
index 0000000..8b45089
--- /dev/null
+++ b/work/work-runtime/src/schemas/androidx.work.impl.WorkDatabase/16.json
@@ -0,0 +1,488 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 16,
+    "identityHash": "5181942b9ebc31ce68dacb56c16fd79f",
+    "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"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_Dependency_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+          },
+          {
+            "name": "index_Dependency_prerequisite_id",
+            "unique": false,
+            "columnNames": [
+              "prerequisite_id"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `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, `last_enqueue_time` INTEGER NOT NULL, `minimum_retention_duration` INTEGER NOT NULL, `schedule_requested_at` INTEGER NOT NULL, `run_in_foreground` INTEGER NOT NULL, `out_of_quota_policy` INTEGER NOT NULL, `period_count` INTEGER NOT NULL DEFAULT 0, `generation` INTEGER NOT NULL DEFAULT 0, `required_network_type` INTEGER NOT NULL, `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, `trigger_content_update_delay` INTEGER NOT NULL, `trigger_max_content_delay` INTEGER NOT NULL, `content_uri_triggers` BLOB NOT NULL, 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": "lastEnqueueTime",
+            "columnName": "last_enqueue_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": "expedited",
+            "columnName": "run_in_foreground",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "outOfQuotaPolicy",
+            "columnName": "out_of_quota_policy",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "periodCount",
+            "columnName": "period_count",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "generation",
+            "columnName": "generation",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "constraints.requiredNetworkType",
+            "columnName": "required_network_type",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.requiresCharging",
+            "columnName": "requires_charging",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.requiresDeviceIdle",
+            "columnName": "requires_device_idle",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.requiresBatteryNotLow",
+            "columnName": "requires_battery_not_low",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.requiresStorageNotLow",
+            "columnName": "requires_storage_not_low",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.contentTriggerUpdateDelayMillis",
+            "columnName": "trigger_content_update_delay",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.contentTriggerMaxDelayMillis",
+            "columnName": "trigger_max_content_delay",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "constraints.contentUriTriggers",
+            "columnName": "content_uri_triggers",
+            "affinity": "BLOB",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_WorkSpec_schedule_requested_at",
+            "unique": false,
+            "columnNames": [
+              "schedule_requested_at"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_WorkSpec_schedule_requested_at` ON `${TABLE_NAME}` (`schedule_requested_at`)"
+          },
+          {
+            "name": "index_WorkSpec_last_enqueue_time",
+            "unique": false,
+            "columnNames": [
+              "last_enqueue_time"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_WorkSpec_last_enqueue_time` ON `${TABLE_NAME}` (`last_enqueue_time`)"
+          }
+        ],
+        "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"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `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, `generation` INTEGER NOT NULL DEFAULT 0, `system_id` INTEGER NOT NULL, PRIMARY KEY(`work_spec_id`, `generation`), 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": "generation",
+            "columnName": "generation",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "0"
+          },
+          {
+            "fieldPath": "systemId",
+            "columnName": "system_id",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "work_spec_id",
+            "generation"
+          ],
+          "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"
+            ],
+            "orders": [],
+            "createSql": "CREATE INDEX IF NOT EXISTS `index_WorkName_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "work_spec_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "WorkProgress",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`work_spec_id` TEXT NOT NULL, `progress` BLOB 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": "progress",
+            "columnName": "progress",
+            "affinity": "BLOB",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "work_spec_id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "WorkSpec",
+            "onDelete": "CASCADE",
+            "onUpdate": "CASCADE",
+            "columns": [
+              "work_spec_id"
+            ],
+            "referencedColumns": [
+              "id"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Preference",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `long_value` INTEGER, PRIMARY KEY(`key`))",
+        "fields": [
+          {
+            "fieldPath": "key",
+            "columnName": "key",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "value",
+            "columnName": "long_value",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "key"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "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, '5181942b9ebc31ce68dacb56c16fd79f')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
index 52deb1d..f3b53c5 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
@@ -23,8 +23,10 @@
 import androidx.work.impl.ExecutionListener
 import androidx.work.impl.Scheduler
 import androidx.work.impl.WorkDatabase
+import androidx.work.impl.model.WorkGenerationalId
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.StartStopTokens
+import androidx.work.impl.model.generationalId
 import androidx.work.impl.model.WorkSpec
 import androidx.work.impl.model.WorkSpecDao
 import java.util.UUID
@@ -54,7 +56,9 @@
         val toSchedule = mutableMapOf<WorkSpec, InternalWorkState>()
         synchronized(lock) {
             workSpecs.forEach {
-                val state = pendingWorkStates.getOrPut(it.id) { InternalWorkState(it) }
+                val state = pendingWorkStates.getOrPut(it.generationalId().workSpecId) {
+                    InternalWorkState(it)
+                }
                 toSchedule[it] = state
             }
         }
@@ -64,7 +68,7 @@
             if (spec.isPeriodic && state.periodDelayMet) {
                 WorkManagerImpl.getInstance(context).rewindLastEnqueueTime(spec.id)
             }
-            scheduleInternal(spec.id, state)
+            scheduleInternal(spec.generationalId(), state)
         }
     }
 
@@ -73,17 +77,19 @@
         // to enqueue() will no-op because insertWorkSpec in WorkDatabase has a conflict
         // policy of @Ignore. So TestScheduler will _never_ be asked to schedule those
         // WorkSpecs.
-        val workRunId = mStartStopTokens.remove(workSpecId)
-        if (workRunId != null) WorkManagerImpl.getInstance(context).stopWork(workRunId)
+        val tokens = mStartStopTokens.remove(workSpecId)
+        tokens.forEach { WorkManagerImpl.getInstance(context).stopWork(it) }
         synchronized(lock) {
-            val internalWorkState = pendingWorkStates[workSpecId]
-            if (internalWorkState != null && !internalWorkState.isPeriodic) {
-                // Don't remove PeriodicWorkRequests from the list of pending work states.
-                // This is because we keep track of mPeriodDelayMet for PeriodicWorkRequests.
-                // `mPeriodDelayMet` is set to `false` when `onExecuted()` is called as a result of a
-                // successful run or a cancellation. That way subsequent calls to schedule() no-op
-                // until a developer explicitly calls setPeriodDelayMet().
-                pendingWorkStates.remove(workSpecId)
+            tokens.forEach { token ->
+                val internalWorkState = pendingWorkStates[token.id.workSpecId]
+                if (internalWorkState != null && !internalWorkState.isPeriodic) {
+                    // Don't remove PeriodicWorkRequests from the list of pending work states.
+                    // This is because we keep track of mPeriodDelayMet for PeriodicWorkRequests.
+                    // `mPeriodDelayMet` is set to `false` when `onExecuted()` is called as a result of a
+                    // successful run or a cancellation. That way subsequent calls to schedule() no-op
+                    // until a developer explicitly calls setPeriodDelayMet().
+                    pendingWorkStates.remove(token.id.workSpecId)
+                }
             }
         }
     }
@@ -105,7 +111,7 @@
             state = oldState.copy(constraintsMet = true)
             pendingWorkStates[id] = state
         }
-        scheduleInternal(id, state)
+        scheduleInternal(WorkGenerationalId(id, state.generation), state)
     }
 
     /**
@@ -126,7 +132,7 @@
             pendingWorkStates[id] = state
         }
         WorkManagerImpl.getInstance(context).rewindLastEnqueueTime(id)
-        scheduleInternal(id, state)
+        scheduleInternal(WorkGenerationalId(id, state.generation), state)
     }
 
     /**
@@ -146,11 +152,12 @@
             pendingWorkStates[id] = state
         }
         WorkManagerImpl.getInstance(context).rewindLastEnqueueTime(id)
-        scheduleInternal(id, state)
+        scheduleInternal(WorkGenerationalId(id, state.generation), state)
     }
 
-    override fun onExecuted(workSpecId: String, needsReschedule: Boolean) {
+    override fun onExecuted(id: WorkGenerationalId, needsReschedule: Boolean) {
         synchronized(lock) {
+            val workSpecId = id.workSpecId
             val internalWorkState = pendingWorkStates[workSpecId] ?: return
             if (internalWorkState.isPeriodic) {
                 pendingWorkStates[workSpecId] = internalWorkState.copy(
@@ -165,15 +172,16 @@
         }
     }
 
-    private fun scheduleInternal(workId: String, state: InternalWorkState) {
+    private fun scheduleInternal(generationalId: WorkGenerationalId, state: InternalWorkState) {
         if (state.isRunnable) {
             val wm = WorkManagerImpl.getInstance(context)
-            wm.startWork(mStartStopTokens.tokenFor(workId))
+            wm.startWork(mStartStopTokens.tokenFor(generationalId))
         }
     }
 }
 
 internal data class InternalWorkState(
+    val generation: Int,
     val constraintsMet: Boolean,
     val initialDelayMet: Boolean,
     val periodDelayMet: Boolean,
@@ -186,6 +194,7 @@
 
 internal fun InternalWorkState(spec: WorkSpec): InternalWorkState =
     InternalWorkState(
+        generation = spec.generation,
         constraintsMet = !spec.hasConstraints(),
         initialDelayMet = spec.initialDelay == 0L,
         periodDelayMet = true,