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,