Store Job data as bytes.

This commit is contained in:
Greyson Parrelli
2023-03-16 15:19:21 -04:00
parent b5af581205
commit 7c8de901f1
198 changed files with 1434 additions and 1302 deletions

View File

@@ -24,10 +24,10 @@ import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
* often they should be retried, and how long they should be retried for.
*
* Never rely on a specific instance of this class being run. It can be created and destroyed as the
* job is retried. State that you want to save is persisted to a {@link Data} object in
* job is retried. State that you want to save is persisted to a {@link JsonJobData} object in
* {@link #serialize()}. Your job is then recreated using a {@link Factory} that you register in
* {@link JobManager.Configuration.Builder#setJobFactories(Map)}, which is given the saved
* {@link Data} bundle.
* {@link JsonJobData} bundle.
*/
public abstract class Job {
@@ -62,11 +62,11 @@ public abstract class Job {
return nextRunAttemptTime;
}
public final @Nullable Data getInputData() {
public final @Nullable byte[] getInputData() {
return parameters.getInputData();
}
public final @NonNull Data requireInputData() {
public final @NonNull byte[] requireInputData() {
return Objects.requireNonNull(parameters.getInputData());
}
@@ -126,7 +126,7 @@ public abstract class Job {
/**
* Serialize your job state so that it can be recreated in the future.
*/
public abstract @NonNull Data serialize();
public abstract @Nullable byte[] serialize();
/**
* Returns the key that can be used to find the relevant factory needed to create your job.
@@ -146,7 +146,7 @@ public abstract class Job {
public abstract void onFailure();
public interface Factory<T extends Job> {
@NonNull T create(@NonNull Parameters parameters, @NonNull Data data);
@NonNull T create(@NonNull Parameters parameters, @Nullable byte[] serializedData);
}
public static final class Result {
@@ -158,10 +158,10 @@ public abstract class Job {
private final ResultType resultType;
private final RuntimeException runtimeException;
private final Data outputData;
private final byte[] outputData;
private final long backoffInterval;
private Result(@NonNull ResultType resultType, @Nullable RuntimeException runtimeException, @Nullable Data outputData, long backoffInterval) {
private Result(@NonNull ResultType resultType, @Nullable RuntimeException runtimeException, @Nullable byte[] outputData, long backoffInterval) {
this.resultType = resultType;
this.runtimeException = runtimeException;
this.outputData = outputData;
@@ -174,7 +174,7 @@ public abstract class Job {
}
/** Job completed successfully and wants to provide some output data. */
public static Result success(@Nullable Data outputData) {
public static Result success(@Nullable byte[] outputData) {
return new Result(ResultType.SUCCESS, null, outputData, INVALID_BACKOFF);
}
@@ -215,7 +215,7 @@ public abstract class Job {
return runtimeException;
}
@Nullable Data getOutputData() {
@Nullable byte[] getOutputData() {
return outputData;
}
@@ -259,7 +259,7 @@ public abstract class Job {
private final int maxInstancesForQueue;
private final String queue;
private final List<String> constraintKeys;
private final Data inputData;
private final byte[] inputData;
private final boolean memoryOnly;
private Parameters(@NonNull String id,
@@ -270,7 +270,7 @@ public abstract class Job {
int maxInstancesForQueue,
@Nullable String queue,
@NonNull List<String> constraintKeys,
@Nullable Data inputData,
@Nullable byte[] inputData,
boolean memoryOnly)
{
this.id = id;
@@ -317,7 +317,7 @@ public abstract class Job {
return constraintKeys;
}
@Nullable Data getInputData() {
@Nullable byte[] getInputData() {
return inputData;
}
@@ -339,7 +339,7 @@ public abstract class Job {
private int maxInstancesForQueue;
private String queue;
private List<String> constraintKeys;
private Data inputData;
private byte[] inputData;
private boolean memoryOnly;
public Builder() {
@@ -358,7 +358,7 @@ public abstract class Job {
int maxInstancesForQueue,
@Nullable String queue,
@NonNull List<String> constraintKeys,
@Nullable Data inputData,
@Nullable byte[] inputData,
boolean memoryOnly)
{
this.id = id;
@@ -465,10 +465,10 @@ public abstract class Job {
}
/**
* Sets the input data that will be made availabe to the job when it is run.
* Sets the input data that will be made available to the job when it is run.
* Should only be set by {@link JobController}.
*/
@NonNull Builder setInputData(@Nullable Data inputData) {
@NonNull Builder setInputData(@Nullable byte[] inputData) {
this.inputData = inputData;
return this;
}

View File

@@ -40,7 +40,6 @@ class JobController {
private final JobStorage jobStorage;
private final JobInstantiator jobInstantiator;
private final ConstraintInstantiator constraintInstantiator;
private final Data.Serializer dataSerializer;
private final JobTracker jobTracker;
private final Scheduler scheduler;
private final Debouncer debouncer;
@@ -51,7 +50,6 @@ class JobController {
@NonNull JobStorage jobStorage,
@NonNull JobInstantiator jobInstantiator,
@NonNull ConstraintInstantiator constraintInstantiator,
@NonNull Data.Serializer dataSerializer,
@NonNull JobTracker jobTracker,
@NonNull Scheduler scheduler,
@NonNull Debouncer debouncer,
@@ -61,7 +59,6 @@ class JobController {
this.jobStorage = jobStorage;
this.jobInstantiator = jobInstantiator;
this.constraintInstantiator = constraintInstantiator;
this.dataSerializer = dataSerializer;
this.jobTracker = jobTracker;
this.scheduler = scheduler;
this.debouncer = debouncer;
@@ -229,7 +226,7 @@ class JobController {
List<JobSpec> updatedJobs = new LinkedList<>();
for (JobSpec job : allJobs) {
JobSpec updated = updater.update(job, dataSerializer);
JobSpec updated = updater.update(job);
if (updated != job) {
updatedJobs.add(updated);
}
@@ -255,7 +252,7 @@ class JobController {
int nextRunAttempt = job.getRunAttempt() + 1;
long nextRunAttemptTime = System.currentTimeMillis() + backoffInterval;
String serializedData = dataSerializer.serialize(job.serialize());
byte[] serializedData = job.serialize();
jobStorage.updateJobAfterRetry(job.getId(), false, nextRunAttempt, nextRunAttemptTime, serializedData);
jobTracker.onStateChange(job, JobTracker.JobState.PENDING);
@@ -279,7 +276,7 @@ class JobController {
}
@WorkerThread
synchronized void onSuccess(@NonNull Job job, @Nullable Data outputData) {
synchronized void onSuccess(@NonNull Job job, @Nullable byte[] outputData) {
if (outputData != null) {
List<JobSpec> updates = Stream.of(jobStorage.getDependencySpecsThatDependOnJob(job.getId()))
.map(DependencySpec::getJobId)
@@ -452,7 +449,7 @@ class JobController {
job.getRunAttempt(),
job.getParameters().getMaxAttempts(),
job.getParameters().getLifespan(),
dataSerializer.serialize(job.serialize()),
job.serialize(),
null,
false,
job.getParameters().isMemoryOnly());
@@ -511,8 +508,7 @@ class JobController {
Job.Parameters parameters = buildJobParameters(jobSpec, constraintSpecs);
try {
Data data = dataSerializer.deserialize(jobSpec.getSerializedData());
Job job = jobInstantiator.instantiate(jobSpec.getFactoryKey(), parameters, data);
Job job = jobInstantiator.instantiate(jobSpec.getFactoryKey(), parameters, jobSpec.getSerializedData());
job.setRunAttempt(jobSpec.getRunAttempt());
job.setNextRunAttemptTime(jobSpec.getNextRunAttemptTime());
@@ -542,11 +538,11 @@ class JobController {
.setMaxAttempts(jobSpec.getMaxAttempts())
.setQueue(jobSpec.getQueueKey())
.setConstraints(Stream.of(constraintSpecs).map(ConstraintSpec::getFactoryKey).toList())
.setInputData(jobSpec.getSerializedInputData() != null ? dataSerializer.deserialize(jobSpec.getSerializedInputData()) : null)
.setInputData(jobSpec.getSerializedInputData())
.build();
}
private @NonNull JobSpec mapToJobWithInputData(@NonNull JobSpec jobSpec, @NonNull Data inputData) {
private @NonNull JobSpec mapToJobWithInputData(@NonNull JobSpec jobSpec, @NonNull byte[] inputData) {
return new JobSpec(jobSpec.getId(),
jobSpec.getFactoryKey(),
jobSpec.getQueueKey(),
@@ -556,7 +552,7 @@ class JobController {
jobSpec.getMaxAttempts(),
jobSpec.getLifespan(),
jobSpec.getSerializedData(),
dataSerializer.serialize(inputData),
inputData,
jobSpec.isRunning(),
jobSpec.isMemoryOnly());
}

View File

@@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.jobmanager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -13,7 +14,7 @@ class JobInstantiator {
this.jobFactories = new HashMap<>(jobFactories);
}
public @NonNull Job instantiate(@NonNull String jobFactoryKey, @NonNull Job.Parameters parameters, @NonNull Data data) {
public @NonNull Job instantiate(@NonNull String jobFactoryKey, @NonNull Job.Parameters parameters, @Nullable byte[] data) {
Job.Factory factory = jobFactories.get(jobFactoryKey);
if (factory != null) {
Job job = factory.create(parameters, data);

View File

@@ -13,7 +13,6 @@ import androidx.annotation.WorkerThread;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.jobmanager.impl.DefaultExecutorFactory;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec;
import org.thoughtcrime.securesms.jobmanager.persistence.JobStorage;
import org.thoughtcrime.securesms.util.Debouncer;
@@ -66,7 +65,6 @@ public class JobManager implements ConstraintObserver.Notifier {
configuration.getJobStorage(),
configuration.getJobInstantiator(),
configuration.getConstraintFactories(),
configuration.getDataSerializer(),
configuration.getJobTracker(),
Build.VERSION.SDK_INT < 26 ? new AlarmManagerScheduler(application)
: new CompositeScheduler(new InAppScheduler(this), new JobSchedulerScheduler(application)),
@@ -78,7 +76,7 @@ public class JobManager implements ConstraintObserver.Notifier {
JobStorage jobStorage = configuration.getJobStorage();
jobStorage.init();
int latestVersion = configuration.getJobMigrator().migrate(jobStorage, configuration.getDataSerializer());
int latestVersion = configuration.getJobMigrator().migrate(jobStorage);
TextSecurePreferences.setJobManagerVersion(application, latestVersion);
jobController.init();
@@ -544,7 +542,6 @@ public class JobManager implements ConstraintObserver.Notifier {
private final JobInstantiator jobInstantiator;
private final ConstraintInstantiator constraintInstantiator;
private final List<ConstraintObserver> constraintObservers;
private final Data.Serializer dataSerializer;
private final JobStorage jobStorage;
private final JobMigrator jobMigrator;
private final JobTracker jobTracker;
@@ -555,7 +552,6 @@ public class JobManager implements ConstraintObserver.Notifier {
@NonNull JobInstantiator jobInstantiator,
@NonNull ConstraintInstantiator constraintInstantiator,
@NonNull List<ConstraintObserver> constraintObservers,
@NonNull Data.Serializer dataSerializer,
@NonNull JobStorage jobStorage,
@NonNull JobMigrator jobMigrator,
@NonNull JobTracker jobTracker,
@@ -566,7 +562,6 @@ public class JobManager implements ConstraintObserver.Notifier {
this.jobInstantiator = jobInstantiator;
this.constraintInstantiator = constraintInstantiator;
this.constraintObservers = new ArrayList<>(constraintObservers);
this.dataSerializer = dataSerializer;
this.jobStorage = jobStorage;
this.jobMigrator = jobMigrator;
this.jobTracker = jobTracker;
@@ -594,10 +589,6 @@ public class JobManager implements ConstraintObserver.Notifier {
return constraintObservers;
}
@NonNull Data.Serializer getDataSerializer() {
return dataSerializer;
}
@NonNull JobStorage getJobStorage() {
return jobStorage;
}
@@ -621,7 +612,6 @@ public class JobManager implements ConstraintObserver.Notifier {
private Map<String, Job.Factory> jobFactories = new HashMap<>();
private Map<String, Constraint.Factory> constraintFactories = new HashMap<>();
private List<ConstraintObserver> constraintObservers = new ArrayList<>();
private Data.Serializer dataSerializer = new JsonDataSerializer();
private JobStorage jobStorage = null;
private JobMigrator jobMigrator = null;
private JobTracker jobTracker = new JobTracker();
@@ -657,11 +647,6 @@ public class JobManager implements ConstraintObserver.Notifier {
return this;
}
public @NonNull Builder setDataSerializer(@NonNull Data.Serializer dataSerializer) {
this.dataSerializer = dataSerializer;
return this;
}
public @NonNull Builder setJobStorage(@NonNull JobStorage jobStorage) {
this.jobStorage = jobStorage;
return this;
@@ -678,7 +663,6 @@ public class JobManager implements ConstraintObserver.Notifier {
new JobInstantiator(jobFactories),
new ConstraintInstantiator(constraintFactories),
new ArrayList<>(constraintObservers),
dataSerializer,
jobStorage,
jobMigrator,
jobTracker,

View File

@@ -31,9 +31,9 @@ public abstract class JobMigration {
private final String factoryKey;
private final String queueKey;
private final Data data;
private final byte[] data;
public JobData(@NonNull String factoryKey, @Nullable String queueKey, @NonNull Data data) {
public JobData(@NonNull String factoryKey, @Nullable String queueKey, @Nullable byte[] data) {
this.factoryKey = factoryKey;
this.queueKey = queueKey;
this.data = data;
@@ -47,7 +47,7 @@ public abstract class JobMigration {
return new JobData(factoryKey, newQueueKey, data);
}
public @NonNull JobData withData(@NonNull Data newData) {
public @NonNull JobData withData(@Nullable byte[] newData) {
return new JobData(factoryKey, queueKey, newData);
}
@@ -59,7 +59,7 @@ public abstract class JobMigration {
return queueKey;
}
public @NonNull Data getData() {
public @NonNull byte[] getData() {
return data;
}
}

View File

@@ -46,7 +46,7 @@ public class JobMigrator {
/**
* @return The version that has been migrated to.
*/
int migrate(@NonNull JobStorage jobStorage, @NonNull Data.Serializer dataSerializer) {
int migrate(@NonNull JobStorage jobStorage) {
List<JobSpec> jobSpecs = jobStorage.getAllJobSpecs();
for (int i = lastSeenVersion; i < currentVersion; i++) {
@@ -58,22 +58,21 @@ public class JobMigrator {
assert migration != null;
while (iter.hasNext()) {
JobSpec jobSpec = iter.next();
Data data = dataSerializer.deserialize(jobSpec.getSerializedData());
JobData originalJobData = new JobData(jobSpec.getFactoryKey(), jobSpec.getQueueKey(), data);
JobData updatedJobData = migration.migrate(originalJobData);
JobSpec updatedJobSpec = new JobSpec(jobSpec.getId(),
updatedJobData.getFactoryKey(),
updatedJobData.getQueueKey(),
jobSpec.getCreateTime(),
jobSpec.getNextRunAttemptTime(),
jobSpec.getRunAttempt(),
jobSpec.getMaxAttempts(),
jobSpec.getLifespan(),
dataSerializer.serialize(updatedJobData.getData()),
jobSpec.getSerializedInputData(),
jobSpec.isRunning(),
jobSpec.isMemoryOnly());
JobSpec jobSpec = iter.next();
JobData originalJobData = new JobData(jobSpec.getFactoryKey(), jobSpec.getQueueKey(), jobSpec.getSerializedData());
JobData updatedJobData = migration.migrate(originalJobData);
JobSpec updatedJobSpec = new JobSpec(jobSpec.getId(),
updatedJobData.getFactoryKey(),
updatedJobData.getQueueKey(),
jobSpec.getCreateTime(),
jobSpec.getNextRunAttemptTime(),
jobSpec.getRunAttempt(),
jobSpec.getMaxAttempts(),
jobSpec.getLifespan(),
updatedJobData.getData(),
jobSpec.getSerializedInputData(),
jobSpec.isRunning(),
jobSpec.isMemoryOnly());
iter.set(updatedJobSpec);
}

View File

@@ -9,11 +9,8 @@ public interface JobUpdater {
* Called for each enqueued job, giving you an opportunity to update each one.
*
* @param jobSpec An object representing data about an enqueued job.
* @param serializer An object that can be used to serialize/deserialize data if necessary for
* your update.
*
* @return The updated JobSpec you want persisted. If you do not wish to make an update, return
* the literal same JobSpec instance you were provided.
* the literal same JobSpec instance you were provided.
*/
@NonNull JobSpec update(@NonNull JobSpec jobSpec, @NonNull Data.Serializer serializer);
@NonNull JobSpec update(@NonNull JobSpec jobSpec);
}

View File

@@ -5,8 +5,12 @@ import androidx.annotation.Nullable;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -14,9 +18,11 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
public class Data {
public class JsonJobData {
public static final Data EMPTY = new Data.Builder().build();
public static final String TAG = Log.tag(JsonJobData.class);
public static final JsonJobData EMPTY = new JsonJobData.Builder().build();
@JsonProperty private final Map<String, String> strings;
@JsonProperty private final Map<String, String[]> stringArrays;
@@ -31,18 +37,31 @@ public class Data {
@JsonProperty private final Map<String, Boolean> booleans;
@JsonProperty private final Map<String, boolean[]> booleanArrays;
public Data(@JsonProperty("strings") @NonNull Map<String, String> strings,
@JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("integers") @NonNull Map<String, Integer> integers,
@JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays,
@JsonProperty("longs") @NonNull Map<String, Long> longs,
@JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays,
@JsonProperty("floats") @NonNull Map<String, Float> floats,
@JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays,
@JsonProperty("doubles") @NonNull Map<String, Double> doubles,
@JsonProperty("doubleArrays") @NonNull Map<String, double[]> doubleArrays,
@JsonProperty("booleans") @NonNull Map<String, Boolean> booleans,
@JsonProperty("booleanArrays") @NonNull Map<String, boolean[]> booleanArrays)
public static @NonNull JsonJobData deserialize(@Nullable byte[] data) {
if (data == null) {
return EMPTY;
}
try {
return JsonUtils.fromJson(data, JsonJobData.class);
} catch (IOException e) {
Log.e(TAG, "Failed to deserialize JSON.", e);
throw new AssertionError(e);
}
}
private JsonJobData(@JsonProperty("strings") @NonNull Map<String, String> strings,
@JsonProperty("stringArrays") @NonNull Map<String, String[]> stringArrays,
@JsonProperty("integers") @NonNull Map<String, Integer> integers,
@JsonProperty("integerArrays") @NonNull Map<String, int[]> integerArrays,
@JsonProperty("longs") @NonNull Map<String, Long> longs,
@JsonProperty("longArrays") @NonNull Map<String, long[]> longArrays,
@JsonProperty("floats") @NonNull Map<String, Float> floats,
@JsonProperty("floatArrays") @NonNull Map<String, float[]> floatArrays,
@JsonProperty("doubles") @NonNull Map<String, Double> doubles,
@JsonProperty("doubleArrays") @NonNull Map<String, double[]> doubleArrays,
@JsonProperty("booleans") @NonNull Map<String, Boolean> booleans,
@JsonProperty("booleanArrays") @NonNull Map<String, boolean[]> booleanArrays)
{
this.strings = strings;
this.stringArrays = stringArrays;
@@ -256,6 +275,34 @@ public class Data {
return new Builder(this);
}
public boolean isEmpty() {
return strings.isEmpty() &&
stringArrays.isEmpty() &&
integers.isEmpty() &&
integerArrays.isEmpty() &&
longs.isEmpty() &&
longArrays.isEmpty() &&
floats.isEmpty() &&
floatArrays.isEmpty() &&
doubles.isEmpty() &&
doubleArrays.isEmpty() &&
booleans.isEmpty() &&
booleanArrays.isEmpty();
}
public @Nullable byte[] serialize() {
if (isEmpty()) {
return null;
} else {
try {
return JsonUtils.toJson(this).getBytes(StandardCharsets.UTF_8);
} catch (IOException e) {
Log.e(TAG, "Failed to serialize to JSON.", e);
throw new AssertionError(e);
}
}
}
public static class Builder {
@@ -274,7 +321,7 @@ public class Data {
public Builder() { }
private Builder(@NonNull Data oldData) {
private Builder(@NonNull JsonJobData oldData) {
strings.putAll(oldData.strings);
stringArrays.putAll(oldData.stringArrays);
integers.putAll(oldData.integers);
@@ -385,24 +432,28 @@ public class Data {
return this;
}
public Data build() {
return new Data(strings,
stringArrays,
integers,
integerArrays,
longs,
longArrays,
floats,
floatArrays,
doubles,
doubleArrays,
booleans,
booleanArrays);
public JsonJobData build() {
return new JsonJobData(strings,
stringArrays,
integers,
integerArrays,
longs,
longArrays,
floats,
floatArrays,
doubles,
doubleArrays,
booleans,
booleanArrays);
}
public @Nullable byte[] serialize() {
return build().serialize();
}
}
public interface Serializer {
@NonNull String serialize(@NonNull Data data);
@NonNull Data deserialize(@NonNull String serialized);
@NonNull String serialize(@NonNull JsonJobData data);
@NonNull JsonJobData deserialize(@NonNull String serialized);
}
}

View File

@@ -1,34 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.impl;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.util.JsonUtils;
import java.io.IOException;
public class JsonDataSerializer implements Data.Serializer {
private static final String TAG = Log.tag(JsonDataSerializer.class);
@Override
public @NonNull String serialize(@NonNull Data data) {
try {
return JsonUtils.toJson(data);
} catch (IOException e) {
Log.e(TAG, "Failed to serialize to JSON.", e);
throw new AssertionError(e);
}
}
@Override
public @NonNull Data deserialize(@NonNull String serialized) {
try {
return JsonUtils.fromJson(serialized, Data.class);
} catch (IOException e) {
Log.e(TAG, "Failed to deserialize JSON.", e);
throw new AssertionError(e);
}
}
}

View File

@@ -8,7 +8,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.NoSuchMessageException;
import org.thoughtcrime.securesms.database.PushTable;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.jobs.FailingJob;
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
@@ -39,16 +39,15 @@ public class PushDecryptMessageJobEnvelopeMigration extends JobMigration {
}
private static @NonNull JobData migratePushDecryptMessageJob(@NonNull PushTable pushDatabase, @NonNull JobData jobData) {
Data data = jobData.getData();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (data.hasLong("message_id")) {
long messageId = data.getLong("message_id");
try {
SignalServiceEnvelope envelope = pushDatabase.get(messageId);
return jobData.withData(jobData.getData()
.buildUpon()
.putBlobAsString("envelope", envelope.serialize())
.build());
return jobData.withData(data.buildUpon()
.putBlobAsString("envelope", envelope.serialize())
.serialize());
} catch (NoSuchMessageException e) {
Log.w(TAG, "Failed to find envelope in DB! Failing.");
return jobData.withFactoryKey(FailingJob.KEY);

View File

@@ -5,14 +5,12 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.groups.BadGroupIdException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.GroupUtil;
import org.whispersystems.signalservice.api.messages.SignalServiceContent;
import java.io.IOException;
@@ -47,7 +45,7 @@ public class PushProcessMessageQueueJobMigration extends JobMigration {
}
private static @NonNull JobData migratePushProcessMessageJob(@NonNull Context context, @NonNull JobData jobData) throws IOException {
Data data = jobData.getData();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
String suffix = "";

View File

@@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.jobmanager.migrations;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
/**
@@ -31,7 +31,9 @@ public class RecipientIdFollowUpJobMigration extends JobMigration {
}
private static @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) {
if (!jobData.getData().hasString("source") || !jobData.getData().hasString("group_id")) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasString("source") || !data.hasString("group_id")) {
return failingJobData();
} else {
return jobData;
@@ -39,9 +41,11 @@ public class RecipientIdFollowUpJobMigration extends JobMigration {
}
private static @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) {
if (!jobData.getData().hasString("recipient") ||
!jobData.getData().hasLong("message_id") ||
!jobData.getData().hasLong("timestamp"))
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasString("recipient") ||
!data.hasLong("message_id") ||
!data.hasLong("timestamp"))
{
return failingJobData();
} else {
@@ -50,6 +54,6 @@ public class RecipientIdFollowUpJobMigration extends JobMigration {
}
private static JobData failingJobData() {
return new JobData("FailingJob", null, new Data.Builder().build());
return new JobData("FailingJob", null, null);
}
}

View File

@@ -7,7 +7,7 @@ import androidx.annotation.VisibleForTesting;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -47,120 +47,142 @@ public class RecipientIdJobMigration extends JobMigration {
}
private @NonNull JobData migrateMultiDeviceContactUpdateJob(@NonNull JobData jobData) {
String address = jobData.getData().hasString("address") ? jobData.getData().getString("address") : null;
Data updatedData = new Data.Builder().putString("recipient", address != null ? Recipient.external(application, address).getId().serialize() : null)
.putBoolean("force_sync", jobData.getData().getBoolean("force_sync"))
.build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.hasString("address") ? data.getString("address") : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", address != null ? Recipient.external(application, address).getId().serialize() : null)
.putBoolean("force_sync", data.getBoolean("force_sync"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceViewOnceOpenJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
try {
String rawOld = jobData.getData().getString("message_id");
String rawOld = data.getString("message_id");
OldSerializableSyncMessageId old = JsonUtils.fromJson(rawOld, OldSerializableSyncMessageId.class);
Recipient recipient = Recipient.external(application, old.sender);
NewSerializableSyncMessageId updated = new NewSerializableSyncMessageId(recipient.getId().serialize(), old.timestamp);
String rawUpdated = JsonUtils.toJson(updated);
Data updatedData = new Data.Builder().putString("message_id", rawUpdated).build();
String rawUpdated = JsonUtils.toJson(updated);
JsonJobData updatedData = new JsonJobData.Builder().putString("message_id", rawUpdated).build();
return jobData.withData(updatedData);
return jobData.withData(updatedData.serialize());
} catch (IOException e) {
throw new AssertionError(e);
}
}
private @NonNull JobData migrateRequestGroupInfoJob(@NonNull JobData jobData) {
String address = jobData.getData().getString("source");
Recipient recipient = Recipient.external(application, address);
Data updatedData = new Data.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", jobData.getData().getString("group_id"))
.build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.getString("source");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", data.getString("group_id"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateSendDeliveryReceiptJob(@NonNull JobData jobData) {
String address = jobData.getData().getString("address");
Recipient recipient = Recipient.external(application, address);
Data updatedData = new Data.Builder().putString("recipient", recipient.getId().serialize())
.putLong("message_id", jobData.getData().getLong("message_id"))
.putLong("timestamp", jobData.getData().getLong("timestamp"))
.build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize())
.putLong("message_id", data.getLong("message_id"))
.putLong("timestamp", data.getLong("timestamp"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceVerifiedUpdateJob(@NonNull JobData jobData) {
String address = jobData.getData().getString("destination");
Recipient recipient = Recipient.external(application, address);
Data updatedData = new Data.Builder().putString("destination", recipient.getId().serialize())
.putString("identity_key", jobData.getData().getString("identity_key"))
.putInt("verified_status", jobData.getData().getInt("verified_status"))
.putLong("timestamp", jobData.getData().getLong("timestamp"))
.build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.getString("destination");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("destination", recipient.getId().serialize())
.putString("identity_key", data.getString("identity_key"))
.putInt("verified_status", data.getInt("verified_status"))
.putLong("timestamp", data.getLong("timestamp"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateRetrieveProfileJob(@NonNull JobData jobData) {
String address = jobData.getData().getString("address");
Recipient recipient = Recipient.external(application, address);
Data updatedData = new Data.Builder().putString("recipient", recipient.getId().serialize()).build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize()).build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migratePushGroupSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
// noinspection ConstantConditions
Recipient queueRecipient = Recipient.external(application, jobData.getQueueKey());
String address = jobData.getData().hasString("filter_address") ? jobData.getData().getString("filter_address") : null;
String address = data.hasString("filter_address") ? data.getString("filter_address") : null;
RecipientId recipientId = address != null ? Recipient.external(application, address).getId() : null;
Data updatedData = new Data.Builder().putString("filter_recipient", recipientId != null ? recipientId.serialize() : null)
.putLong("message_id", jobData.getData().getLong("message_id"))
.build();
JsonJobData updatedData = new JsonJobData.Builder().putString("filter_recipient", recipientId != null ? recipientId.serialize() : null)
.putLong("message_id", data.getLong("message_id"))
.build();
return jobData.withQueueKey(queueRecipient.getId().toQueueKey())
.withData(updatedData);
.withData(updatedData.serialize());
}
private @NonNull JobData migratePushGroupUpdateJob(@NonNull JobData jobData) {
String address = jobData.getData().getString("source");
Recipient recipient = Recipient.external(application, address);
Data updatedData = new Data.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", jobData.getData().getString("group_id"))
.build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.getString("source");
Recipient recipient = Recipient.external(application, address);
JsonJobData updatedData = new JsonJobData.Builder().putString("source", recipient.getId().serialize())
.putString("group_id", data.getString("group_id"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateDirectoryRefreshJob(@NonNull JobData jobData) {
String address = jobData.getData().hasString("address") ? jobData.getData().getString("address") : null;
Recipient recipient = address != null ? Recipient.external(application, address) : null;
Data updatedData = new Data.Builder().putString("recipient", recipient != null ? recipient.getId().serialize() : null)
.putBoolean("notify_of_new_users", jobData.getData().getBoolean("notify_of_new_users"))
.build();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
return jobData.withData(updatedData);
String address = data.hasString("address") ? data.getString("address") : null;
Recipient recipient = address != null ? Recipient.external(application, address) : null;
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient != null ? recipient.getId().serialize() : null)
.putBoolean("notify_of_new_users", data.getBoolean("notify_of_new_users"))
.build();
return jobData.withData(updatedData.serialize());
}
private @NonNull JobData migrateRetrieveProfileAvatarJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
String queueAddress = jobData.getQueueKey().substring("RetrieveProfileAvatarJob".length());
Recipient queueRecipient = Recipient.external(application, queueAddress);
String address = jobData.getData().getString("address");
String address = data.getString("address");
Recipient recipient = Recipient.external(application, address);
Data updatedData = new Data.Builder().putString("recipient", recipient.getId().serialize())
.putString("profile_avatar", jobData.getData().getString("profile_avatar"))
.build();
JsonJobData updatedData = new JsonJobData.Builder().putString("recipient", recipient.getId().serialize())
.putString("profile_avatar", data.getString("profile_avatar"))
.build();
return jobData.withQueueKey("RetrieveProfileAvatarJob::" + queueRecipient.getId().toQueueKey())
.withData(updatedData);
.withData(updatedData.serialize());
}
private @NonNull JobData migrateMultiDeviceReadUpdateJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
try {
String[] rawOld = jobData.getData().getStringArray("message_ids");
String[] rawOld = data.getStringArray("message_ids");
String[] rawUpdated = new String[rawOld.length];
for (int i = 0; i < rawOld.length; i++) {
@@ -171,27 +193,33 @@ public class RecipientIdJobMigration extends JobMigration {
rawUpdated[i] = JsonUtils.toJson(updated);
}
Data updatedData = new Data.Builder().putStringArray("message_ids", rawUpdated).build();
JsonJobData updatedData = new JsonJobData.Builder().putStringArray("message_ids", rawUpdated).build();
return jobData.withData(updatedData);
return jobData.withData(updatedData.serialize());
} catch (IOException e) {
throw new AssertionError(e);
}
}
private @NonNull JobData migratePushTextSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
}
private @NonNull JobData migratePushMediaSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
Recipient recipient = Recipient.external(application, jobData.getQueueKey());
return jobData.withQueueKey(recipient.getId().toQueueKey());
}
private @NonNull JobData migrateSmsSendJob(@NonNull JobData jobData) {
JsonJobData data = JsonJobData.deserialize(jobData.getData());
//noinspection ConstantConditions
if (jobData.getQueueKey() != null) {
Recipient recipient = Recipient.external(application, jobData.getQueueKey());

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.jobmanager.migrations;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
public class RetrieveProfileJobMigration extends JobMigration {
@@ -25,15 +25,15 @@ public class RetrieveProfileJobMigration extends JobMigration {
}
private static @NonNull JobData migrateRetrieveProfileJob(@NonNull JobData jobData) {
Data data = jobData.getData();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (data.hasString("recipient")) {
Log.i(TAG, "Migrating job.");
String recipient = data.getString("recipient");
return jobData.withData(new Data.Builder()
return jobData.withData(new JsonJobData.Builder()
.putStringArray("recipients", new String[] { recipient })
.build());
.serialize());
} else {
return jobData;
}

View File

@@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.jobmanager.migrations;
import androidx.annotation.NonNull;
import org.thoughtcrime.securesms.database.MessageTable;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import java.util.SortedSet;
@@ -27,10 +27,10 @@ public class SendReadReceiptsJobMigration extends JobMigration {
}
private static @NonNull JobData migrateSendReadReceiptJob(@NonNull MessageTable messageTable, @NonNull JobData jobData) {
Data data = jobData.getData();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (!data.hasLong("thread")) {
long[] messageIds = jobData.getData().getLongArray("message_ids");
long[] messageIds = data.getLongArray("message_ids");
SortedSet<Long> threadIds = new TreeSet<>();
for (long id : messageIds) {
@@ -41,9 +41,9 @@ public class SendReadReceiptsJobMigration extends JobMigration {
}
if (threadIds.size() != 1) {
return new JobData("FailingJob", null, new Data.Builder().build());
return new JobData("FailingJob", null, null);
} else {
return jobData.withData(data.buildUpon().putLong("thread", threadIds.first()).build());
return jobData.withData(data.buildUpon().putLong("thread", threadIds.first()).serialize());
}
} else {

View File

@@ -8,7 +8,7 @@ import org.thoughtcrime.securesms.database.GroupTable;
import org.thoughtcrime.securesms.database.model.GroupRecord;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.JsonJobData;
import org.thoughtcrime.securesms.jobmanager.JobMigration;
import org.thoughtcrime.securesms.jobs.FailingJob;
@@ -44,7 +44,7 @@ public class SenderKeyDistributionSendJobRecipientMigration extends JobMigration
}
private static @NonNull JobData migrateJob(@NonNull JobData jobData, @NonNull GroupTable groupDatabase) {
Data data = jobData.getData();
JsonJobData data = JsonJobData.deserialize(jobData.getData());
if (data.hasString("group_id")) {
GroupId groupId = GroupId.pushOrThrow(data.getStringAsBlob("group_id"));
@@ -53,7 +53,7 @@ public class SenderKeyDistributionSendJobRecipientMigration extends JobMigration
if (group.isPresent()) {
return jobData.withData(data.buildUpon()
.putString("thread_recipient_id", group.get().getRecipientId().serialize())
.build());
.serialize());
} else {
return jobData.withFactoryKey(FailingJob.KEY);

View File

@@ -1,139 +0,0 @@
package org.thoughtcrime.securesms.jobmanager.persistence;
import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Locale;
import java.util.Objects;
public final class JobSpec {
private final String id;
private final String factoryKey;
private final String queueKey;
private final long createTime;
private final long nextRunAttemptTime;
private final int runAttempt;
private final int maxAttempts;
private final long lifespan;
private final String serializedData;
private final String serializedInputData;
private final boolean isRunning;
private final boolean memoryOnly;
public JobSpec(@NonNull String id,
@NonNull String factoryKey,
@Nullable String queueKey,
long createTime,
long nextRunAttemptTime,
int runAttempt,
int maxAttempts,
long lifespan,
@NonNull String serializedData,
@Nullable String serializedInputData,
boolean isRunning,
boolean memoryOnly)
{
this.id = id;
this.factoryKey = factoryKey;
this.queueKey = queueKey;
this.createTime = createTime;
this.nextRunAttemptTime = nextRunAttemptTime;
this.runAttempt = runAttempt;
this.maxAttempts = maxAttempts;
this.lifespan = lifespan;
this.serializedData = serializedData;
this.serializedInputData = serializedInputData;
this.isRunning = isRunning;
this.memoryOnly = memoryOnly;
}
public @NonNull JobSpec withNextRunAttemptTime(long updated) {
return new JobSpec(id, factoryKey, queueKey, createTime, updated, runAttempt, maxAttempts, lifespan, serializedData, serializedInputData, isRunning, memoryOnly);
}
public @NonNull JobSpec withData(String updatedSerializedData) {
return new JobSpec(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, lifespan, updatedSerializedData, serializedInputData, isRunning, memoryOnly);
}
public @NonNull String getId() {
return id;
}
public @NonNull String getFactoryKey() {
return factoryKey;
}
public @Nullable String getQueueKey() {
return queueKey;
}
public long getCreateTime() {
return createTime;
}
public long getNextRunAttemptTime() {
return nextRunAttemptTime;
}
public int getRunAttempt() {
return runAttempt;
}
public int getMaxAttempts() {
return maxAttempts;
}
public long getLifespan() {
return lifespan;
}
public @NonNull String getSerializedData() {
return serializedData;
}
public @Nullable String getSerializedInputData() {
return serializedInputData;
}
public boolean isRunning() {
return isRunning;
}
public boolean isMemoryOnly() {
return memoryOnly;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JobSpec jobSpec = (JobSpec) o;
return createTime == jobSpec.createTime &&
nextRunAttemptTime == jobSpec.nextRunAttemptTime &&
runAttempt == jobSpec.runAttempt &&
maxAttempts == jobSpec.maxAttempts &&
lifespan == jobSpec.lifespan &&
isRunning == jobSpec.isRunning &&
memoryOnly == jobSpec.memoryOnly &&
Objects.equals(id, jobSpec.id) &&
Objects.equals(factoryKey, jobSpec.factoryKey) &&
Objects.equals(queueKey, jobSpec.queueKey) &&
Objects.equals(serializedData, jobSpec.serializedData) &&
Objects.equals(serializedInputData, jobSpec.serializedInputData);
}
@Override
public int hashCode() {
return Objects.hash(id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, lifespan, serializedData, serializedInputData, isRunning, memoryOnly);
}
@SuppressLint("DefaultLocale")
@Override
public @NonNull String toString() {
return String.format(Locale.US, "id: JOB::%s | factoryKey: %s | queueKey: %s | createTime: %d | nextRunAttemptTime: %d | runAttempt: %d | maxAttempts: %d | lifespan: %d | isRunning: %b | memoryOnly: %b",
id, factoryKey, queueKey, createTime, nextRunAttemptTime, runAttempt, maxAttempts, lifespan, isRunning, memoryOnly);
}
}

View File

@@ -0,0 +1,73 @@
package org.thoughtcrime.securesms.jobmanager.persistence
data class JobSpec(
val id: String,
val factoryKey: String,
val queueKey: String?,
val createTime: Long,
val nextRunAttemptTime: Long,
val runAttempt: Int,
val maxAttempts: Int,
val lifespan: Long,
val serializedData: ByteArray?,
val serializedInputData: ByteArray?,
val isRunning: Boolean,
val isMemoryOnly: Boolean
) {
fun withNextRunAttemptTime(updated: Long): JobSpec {
return copy(nextRunAttemptTime = updated)
}
fun withData(updatedSerializedData: ByteArray?): JobSpec {
return copy(serializedData = updatedSerializedData)
}
override fun toString(): String {
return "id: JOB::$id | factoryKey: $factoryKey | queueKey: $queueKey | createTime: $createTime | nextRunAttemptTime: $nextRunAttemptTime | runAttempt: $runAttempt | maxAttempts: $maxAttempts | lifespan: $lifespan | isRunning: $isRunning | memoryOnly: $isMemoryOnly"
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as JobSpec
if (id != other.id) return false
if (factoryKey != other.factoryKey) return false
if (queueKey != other.queueKey) return false
if (createTime != other.createTime) return false
if (nextRunAttemptTime != other.nextRunAttemptTime) return false
if (runAttempt != other.runAttempt) return false
if (maxAttempts != other.maxAttempts) return false
if (lifespan != other.lifespan) return false
if (serializedData != null) {
if (other.serializedData == null) return false
if (!serializedData.contentEquals(other.serializedData)) return false
} else if (other.serializedData != null) return false
if (serializedInputData != null) {
if (other.serializedInputData == null) return false
if (!serializedInputData.contentEquals(other.serializedInputData)) return false
} else if (other.serializedInputData != null) return false
if (isRunning != other.isRunning) return false
if (isMemoryOnly != other.isMemoryOnly) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + factoryKey.hashCode()
result = 31 * result + (queueKey?.hashCode() ?: 0)
result = 31 * result + createTime.hashCode()
result = 31 * result + nextRunAttemptTime.hashCode()
result = 31 * result + runAttempt
result = 31 * result + maxAttempts
result = 31 * result + lifespan.hashCode()
result = 31 * result + (serializedData?.contentHashCode() ?: 0)
result = 31 * result + (serializedInputData?.contentHashCode() ?: 0)
result = 31 * result + isRunning.hashCode()
result = 31 * result + isMemoryOnly.hashCode()
return result
}
}

View File

@@ -40,7 +40,7 @@ public interface JobStorage {
void updateJobRunningState(@NonNull String id, boolean isRunning);
@WorkerThread
void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime, @NonNull String serializedData);
void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime, @Nullable byte[] serializedData);
@WorkerThread
void updateAllJobsToBePending();