Correctly reschedule periodic work on API < 23.
- Fixes an issue where periodic workers are not correctly rescheduled.
- Enables ForceStopRunnable to reschedule workers on initialization
so that Workers are in a good state post upgrade to alpha04.
- Use migrations to help determine if we need to reschedule.
Test: Updated unit tests.
Fixes: b/110698000
Change-Id: Icb47403ef2aee32effefa88cceb3c7d0435bdc9f
diff --git a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
index 15bf224..4e62ced 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/WorkDatabaseMigrationTest.java
@@ -25,6 +25,7 @@
import android.arch.persistence.db.framework.FrameworkSQLiteOpenHelperFactory;
import android.arch.persistence.room.testing.MigrationTestHelper;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.support.test.InstrumentationRegistry;
@@ -35,6 +36,7 @@
import androidx.work.impl.WorkDatabaseMigrations;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkTypeConverters;
+import androidx.work.impl.utils.Preferences;
import androidx.work.worker.TestWorker;
import org.junit.Before;
@@ -51,8 +53,9 @@
private static final String TEST_DATABASE = "workdatabase-test";
private static final boolean VALIDATE_DROPPED_TABLES = true;
- private static final int OLD_VERSION = 1;
- private static final int NEW_VERSION = 2;
+ private static final int VERSION_1 = 1;
+ private static final int VERSION_2 = 2;
+ private static final int VERSION_3 = 3;
private static final String COLUMN_WORKSPEC_ID = "work_spec_id";
private static final String COLUMN_SYSTEM_ID = "system_id";
private static final String COLUMN_ALARM_ID = "alarm_id";
@@ -69,6 +72,7 @@
private static final String TABLE_WORKTAG = "WorkTag";
private static final String TABLE_WORKNAME = "WorkName";
+ private Context mContext;
private File mDatabasePath;
@Rule
@@ -80,6 +84,7 @@
@Before
public void setUp() {
// Delete the database if it exists.
+ mContext = InstrumentationRegistry.getTargetContext();
mDatabasePath = InstrumentationRegistry.getContext().getDatabasePath(TEST_DATABASE);
if (mDatabasePath.exists()) {
mDatabasePath.delete();
@@ -90,7 +95,7 @@
@MediumTest
public void testMigrationVersion1To2() throws IOException {
SupportSQLiteDatabase database =
- mMigrationTestHelper.createDatabase(TEST_DATABASE, OLD_VERSION);
+ mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_1);
String[] prepopulatedWorkSpecIds = new String[] {
UUID.randomUUID().toString(),
@@ -142,7 +147,7 @@
database = mMigrationTestHelper.runMigrationsAndValidate(
TEST_DATABASE,
- NEW_VERSION,
+ VERSION_2,
VALIDATE_DROPPED_TABLES,
WorkDatabaseMigrations.MIGRATION_1_2);
@@ -183,6 +188,25 @@
database.close();
}
+ @Test
+ @MediumTest
+ public void testMigrationVersion2To3() throws IOException {
+ SupportSQLiteDatabase database =
+ mMigrationTestHelper.createDatabase(TEST_DATABASE, VERSION_2);
+ WorkDatabaseMigrations.Migration2To3 migration2To3 = new WorkDatabaseMigrations
+ .Migration2To3(mContext);
+
+ database = mMigrationTestHelper.runMigrationsAndValidate(
+ TEST_DATABASE,
+ VERSION_3,
+ VALIDATE_DROPPED_TABLES,
+ migration2To3);
+
+ Preferences preferences = new Preferences(mContext);
+ assertThat(preferences.needsReschedule(), is(true));
+ database.close();
+ }
+
private boolean checkExists(SupportSQLiteDatabase database, String tableName) {
Cursor cursor = null;
try {
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index 3e01164..ba9fe04 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -1405,6 +1405,7 @@
WorkDatabase.generateCleanupCallback().onOpen(db);
assertThat(workSpecDao.getState(work.getStringId()), is(ENQUEUED));
+ assertThat(work.getWorkSpec().scheduleRequestedAt, is(WorkSpec.SCHEDULE_NOT_REQUESTED_YET));
}
@Test
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
index 739fcf7..3dae0f9 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/WorkerWrapperTest.java
@@ -31,6 +31,7 @@
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.greaterThan;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -38,6 +39,7 @@
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.LargeTest;
import android.support.test.filters.SmallTest;
@@ -507,6 +509,7 @@
insertWork(periodicWork);
new WorkerWrapper.Builder(mContext, mConfiguration, mDatabase, periodicWorkId)
.withListener(mMockListener)
+ .withSchedulers(Collections.singletonList(mMockScheduler))
.build()
.run();
@@ -514,6 +517,16 @@
verify(mMockListener).onExecuted(periodicWorkId, true, false);
assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
+
+ // SystemAlarmScheduler needs to reschedule the same worker.
+ if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL) {
+ ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
+ verify(mMockScheduler, atLeast(1))
+ .schedule(captor.capture());
+
+ WorkSpec workSpec = captor.getValue();
+ assertThat(workSpec.id, is(periodicWorkId));
+ }
}
@Test
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
index cfe2cef..2944087 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/background/systemalarm/SystemAlarmDispatcherTest.java
@@ -389,6 +389,7 @@
// Use a mocked scheduler in this test.
Scheduler scheduler = mock(Scheduler.class);
doCallRealMethod().when(mWorkManager).rescheduleEligibleWork();
+ when(mWorkManager.getApplicationContext()).thenReturn(mContext);
when(mWorkManager.getSchedulers()).thenReturn(Collections.singletonList(scheduler));
OneTimeWorkRequest failed = new OneTimeWorkRequest.Builder(TestWorker.class)
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
index fec3878..c706e40 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
@@ -16,11 +16,8 @@
package androidx.work.impl.utils;
-import static android.content.Context.MODE_PRIVATE;
-
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -30,9 +27,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SdkSuppress;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -48,8 +43,6 @@
@RunWith(AndroidJUnit4.class)
public class ForceStopRunnableTest {
- private static final String PREFERENCES_FILE_NAME = "androidx.work.util.preferences.test";
-
private Context mContext;
private WorkManagerImpl mWorkManager;
private WorkDatabase mWorkDatabase;
@@ -82,7 +75,7 @@
@Test
public void testReschedulesOnForceStop() {
ForceStopRunnable runnable = spy(mRunnable);
- when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
+ when(runnable.shouldRescheduleWorkers()).thenReturn(false);
when(runnable.isForceStopped()).thenReturn(true);
runnable.run();
verify(mWorkManager, times(1)).rescheduleEligibleWork();
@@ -91,45 +84,17 @@
@Test
public void test_doNothingWhenNotForceStopped() {
ForceStopRunnable runnable = spy(mRunnable);
- when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
+ when(runnable.shouldRescheduleWorkers()).thenReturn(false);
when(runnable.isForceStopped()).thenReturn(false);
runnable.run();
verify(mWorkManager, times(0)).rescheduleEligibleWork();
}
@Test
- public void test_cancelAllJobSchedulerJobs() {
+ public void test_rescheduleWorkers_updatesSharedPreferences() {
ForceStopRunnable runnable = spy(mRunnable);
- doNothing().when(runnable).cancelAllInJobScheduler();
- when(runnable.shouldCancelPersistedJobs()).thenReturn(true);
+ when(runnable.shouldRescheduleWorkers()).thenReturn(true);
runnable.run();
- verify(runnable, times(1)).cancelAllInJobScheduler();
- verify(mPreferences, times(1)).setMigratedPersistedJobs();
- }
-
- @Test
- public void test_doNothingWhenThereIsNothingToCancel() {
- ForceStopRunnable runnable = spy(mRunnable);
- doNothing().when(runnable).cancelAllInJobScheduler();
- when(runnable.shouldCancelPersistedJobs()).thenReturn(false);
- runnable.run();
- verify(runnable, times(0)).cancelAllInJobScheduler();
- }
-
- @Test
- @SdkSuppress(minSdkVersion = WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
- public void testMigratePersistedJobs() {
- SharedPreferences testSharedPreferences =
- mContext.getSharedPreferences(PREFERENCES_FILE_NAME, MODE_PRIVATE);
- testSharedPreferences.edit()
- .clear()
- .apply();
- Preferences testPreferences = new Preferences(testSharedPreferences);
- when(mWorkManager.getPreferences()).thenReturn(testPreferences);
- ForceStopRunnable runnable = spy(mRunnable);
- doNothing().when(runnable).cancelAllInJobScheduler();
- assertThat(runnable.shouldCancelPersistedJobs(), is(true));
- runnable.run();
- assertThat(runnable.shouldCancelPersistedJobs(), is(false));
+ verify(mPreferences, times(1)).setNeedsReschedule(false);
}
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
index 4944549..a31bf46 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/Schedulers.java
@@ -73,6 +73,8 @@
scheduleInternal(workDatabase, schedulers, eligibleWorkSpecs);
}
+
+
private static void scheduleInternal(
@NonNull WorkDatabase workDatabase,
List<Scheduler> schedulers,
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
index 6a1cc1e..e918906 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabase.java
@@ -56,12 +56,14 @@
WorkTag.class,
SystemIdInfo.class,
WorkName.class},
- version = 2)
+ version = 3)
@TypeConverters(value = {Data.class, WorkTypeConverters.class})
public abstract class WorkDatabase extends RoomDatabase {
private static final String DB_NAME = "androidx.work.workdb";
- private static final String CLEANUP_SQL = "UPDATE workspec SET state=" + ENQUEUED
+ private static final String CLEANUP_SQL = "UPDATE workspec "
+ + "SET state=" + ENQUEUED + ","
+ + " schedule_requested_at=" + WorkSpec.SCHEDULE_NOT_REQUESTED_YET
+ " WHERE state=" + RUNNING;
// Delete rows in the workspec table that...
@@ -97,6 +99,7 @@
}
return builder.addCallback(generateCleanupCallback())
.addMigrations(WorkDatabaseMigrations.MIGRATION_1_2)
+ .addMigrations(new WorkDatabaseMigrations.Migration2To3(context))
.fallbackToDestructiveMigration()
.build();
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
index 935561b..86d503d 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkDatabaseMigrations.java
@@ -18,9 +18,12 @@
import android.arch.persistence.db.SupportSQLiteDatabase;
import android.arch.persistence.room.migration.Migration;
+import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;
+import androidx.work.impl.utils.Preferences;
+
/**
* Migration helpers for {@link androidx.work.impl.WorkDatabase}.
*
@@ -36,6 +39,7 @@
// Known WorkDatabase versions
private static final int VERSION_1 = 1;
private static final int VERSION_2 = 2;
+ private static final int VERSION_3 = 3;
private static final String CREATE_SYSTEM_ID_INFO =
"CREATE TABLE IF NOT EXISTS `SystemIdInfo` (`work_spec_id` TEXT NOT NULL, `system_id`"
@@ -63,4 +67,22 @@
+ "SELECT worker_class_name AS tag, id AS work_spec_id FROM workspec");
}
};
+
+ /**
+ * Migrates {@link WorkDatabase} version 2 to 3.
+ */
+ public static class Migration2To3 extends Migration {
+ final Context mContext;
+
+ public Migration2To3(@NonNull Context context) {
+ super(VERSION_2, VERSION_3);
+ mContext = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ Preferences preferences = new Preferences(mContext);
+ preferences.setNeedsReschedule(true);
+ }
+ }
}
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 88d458a..6e97b53 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -19,6 +19,7 @@
import android.arch.core.util.Function;
import android.arch.lifecycle.LiveData;
import android.content.Context;
+import android.os.Build;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -37,6 +38,7 @@
import androidx.work.WorkRequest;
import androidx.work.WorkStatus;
import androidx.work.impl.background.greedy.GreedyScheduler;
+import androidx.work.impl.background.systemjob.SystemJobScheduler;
import androidx.work.impl.model.WorkSpec;
import androidx.work.impl.model.WorkSpecDao;
import androidx.work.impl.utils.CancelWorkRunnable;
@@ -511,6 +513,11 @@
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public void rescheduleEligibleWork() {
+ // TODO (rahulrav@) Make every scheduler do its own cancelAll().
+ if (Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL) {
+ SystemJobScheduler.jobSchedulerCancelAll(getApplicationContext());
+ }
+
// Reset scheduled state.
getWorkDatabase().workSpecDao().resetScheduledState();
diff --git a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
index cd1241b..eed0ca4 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/WorkerWrapper.java
@@ -23,6 +23,7 @@
import static androidx.work.State.SUCCEEDED;
import android.content.Context;
+import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
@@ -346,11 +347,20 @@
mWorkSpecDao.setPeriodStartTime(mWorkSpecId, nextPeriodStartTime);
mWorkSpecDao.setState(ENQUEUED, mWorkSpecId);
mWorkSpecDao.resetWorkSpecRunAttemptCount(mWorkSpecId);
+ // We need to tell the schedulers that this WorkSpec is no longer occupying a slot.
+ mWorkSpecDao.markWorkSpecScheduled(mWorkSpecId, WorkSpec.SCHEDULE_NOT_REQUESTED_YET);
mWorkDatabase.setTransactionSuccessful();
} finally {
mWorkDatabase.endTransaction();
notifyListener(isSuccessful, false);
}
+
+ // We need to tell the Schedulers to pick up this newly ENQUEUED Worker.
+ // TODO (rahulrav@) Move this into the Scheduler itself.
+ if (Build.VERSION.SDK_INT <= WorkManagerImpl.MAX_PRE_JOB_SCHEDULER_API_LEVEL) {
+ // Reschedule the periodic work.
+ Schedulers.schedule(mConfiguration, mWorkDatabase, mSchedulers);
+ }
}
private void setSucceededAndNotify() {
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index ab6b067..fbcaa42 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
@@ -20,10 +20,8 @@
import static android.app.PendingIntent.FLAG_NO_CREATE;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
-import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.PendingIntent;
-import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -34,7 +32,6 @@
import android.util.Log;
import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.background.systemjob.SystemJobScheduler;
import java.util.concurrent.TimeUnit;
@@ -67,12 +64,11 @@
@Override
public void run() {
- if (shouldCancelPersistedJobs()) {
- cancelAllInJobScheduler();
- Log.d(TAG, "Migrating persisted jobs.");
+ if (shouldRescheduleWorkers()) {
+ Log.d(TAG, "Rescheduling Workers.");
mWorkManager.rescheduleEligibleWork();
// Mark the jobs as migrated.
- mWorkManager.getPreferences().setMigratedPersistedJobs();
+ mWorkManager.getPreferences().setNeedsReschedule(false);
} else if (isForceStopped()) {
Log.d(TAG, "Application was force-stopped, rescheduling.");
mWorkManager.rescheduleEligibleWork();
@@ -98,12 +94,11 @@
}
/**
- * @return {@code true} If persisted jobs in JobScheduler need to be cancelled.
+ * @return {@code true} If we need to reschedule Workers.
*/
@VisibleForTesting
- public boolean shouldCancelPersistedJobs() {
- return Build.VERSION.SDK_INT >= WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL
- && mWorkManager.getPreferences().shouldMigratePersistedJobs();
+ public boolean shouldRescheduleWorkers() {
+ return mWorkManager.getPreferences().needsReschedule();
}
/**
@@ -128,15 +123,6 @@
return intent;
}
- /**
- * Cancels all the persisted jobs in {@link JobScheduler}.
- */
- @VisibleForTesting
- @TargetApi(WorkManagerImpl.MIN_JOB_SCHEDULER_API_LEVEL)
- public void cancelAllInJobScheduler() {
- SystemJobScheduler.jobSchedulerCancelAll(mContext);
- }
-
void setAlarm(int alarmId) {
AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
// Using FLAG_UPDATE_CURRENT, because we only ever want once instance of this alarm.
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
index 529a2b4..d8b9484 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/Preferences.java
@@ -37,7 +37,7 @@
private static final String PREFERENCES_FILE_NAME = "androidx.work.util.preferences";
private static final String KEY_LAST_CANCEL_ALL_TIME_MS = "last_cancel_all_time_ms";
- private static final String KEY_MIGRATE_PERSISTED_JOBS = "migrate_persisted_jobs";
+ private static final String KEY_RESCHEDULE_NEEDED = "reschedule_needed";
private SharedPreferences mSharedPreferences;
@@ -46,7 +46,7 @@
}
@VisibleForTesting
- Preferences(@NonNull SharedPreferences preferences) {
+ public Preferences(@NonNull SharedPreferences preferences) {
mSharedPreferences = preferences;
}
@@ -75,20 +75,19 @@
}
/**
- * @return {@code true} When we should migrate from persisted jobs to non-persisted jobs in
- * {@link android.app.job.JobScheduler}
+ * @return {@code true} When we should reschedule workers.
*/
- public boolean shouldMigratePersistedJobs() {
+ public boolean needsReschedule() {
+ // This preference is being set by a Room Migration.
// TODO Remove this before WorkManager 1.0 beta.
- return mSharedPreferences.getBoolean(KEY_MIGRATE_PERSISTED_JOBS, true);
+ return mSharedPreferences.getBoolean(KEY_RESCHEDULE_NEEDED, false);
}
/**
- * Updates the key which indicates that we have migrated all our persisted jobs in
- * {@link android.app.job.JobScheduler}.
+ * Updates the key which indicates that we have rescheduled jobs.
*/
- public void setMigratedPersistedJobs() {
- mSharedPreferences.edit().putBoolean(KEY_MIGRATE_PERSISTED_JOBS, false).apply();
+ public void setNeedsReschedule(boolean needsReschedule) {
+ mSharedPreferences.edit().putBoolean(KEY_RESCHEDULE_NEEDED, needsReschedule).apply();
}
/**
diff --git a/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/3.json b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/3.json
new file mode 100644
index 0000000..69e73eb
--- /dev/null
+++ b/work/workmanager/src/schemas/androidx.work.impl.WorkDatabase/3.json
@@ -0,0 +1,363 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 3,
+ "identityHash": "c45e5fcbdf3824dead9778f19e2fd8af",
+ "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"
+ ],
+ "createSql": "CREATE INDEX `index_Dependency_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+ },
+ {
+ "name": "index_Dependency_prerequisite_id",
+ "unique": false,
+ "columnNames": [
+ "prerequisite_id"
+ ],
+ "createSql": "CREATE INDEX `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, `period_start_time` INTEGER NOT NULL, `minimum_retention_duration` INTEGER NOT NULL, `schedule_requested_at` INTEGER NOT NULL, `required_network_type` INTEGER, `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, `content_uri_triggers` BLOB, 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": "periodStartTime",
+ "columnName": "period_start_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": "constraints.mRequiredNetworkType",
+ "columnName": "required_network_type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "constraints.mRequiresCharging",
+ "columnName": "requires_charging",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "constraints.mRequiresDeviceIdle",
+ "columnName": "requires_device_idle",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "constraints.mRequiresBatteryNotLow",
+ "columnName": "requires_battery_not_low",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "constraints.mRequiresStorageNotLow",
+ "columnName": "requires_storage_not_low",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "constraints.mContentUriTriggers",
+ "columnName": "content_uri_triggers",
+ "affinity": "BLOB",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_WorkSpec_schedule_requested_at",
+ "unique": false,
+ "columnNames": [
+ "schedule_requested_at"
+ ],
+ "createSql": "CREATE INDEX `index_WorkSpec_schedule_requested_at` ON `${TABLE_NAME}` (`schedule_requested_at`)"
+ }
+ ],
+ "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"
+ ],
+ "createSql": "CREATE INDEX `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, `system_id` INTEGER 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": "systemId",
+ "columnName": "system_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "work_spec_id"
+ ],
+ "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"
+ ],
+ "createSql": "CREATE INDEX `index_WorkName_work_spec_id` ON `${TABLE_NAME}` (`work_spec_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "WorkSpec",
+ "onDelete": "CASCADE",
+ "onUpdate": "CASCADE",
+ "columns": [
+ "work_spec_id"
+ ],
+ "referencedColumns": [
+ "id"
+ ]
+ }
+ ]
+ }
+ ],
+ "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, \"c45e5fcbdf3824dead9778f19e2fd8af\")"
+ ]
+ }
+}
\ No newline at end of file