kopia lustrzana https://github.com/ryukoposting/Signal-Android
482 wiersze
15 KiB
Java
482 wiersze
15 KiB
Java
package org.thoughtcrime.securesms.jobmanager;
|
|
|
|
import android.content.Context;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.annotation.VisibleForTesting;
|
|
import androidx.annotation.WorkerThread;
|
|
|
|
import org.signal.core.util.logging.Log;
|
|
|
|
import java.util.LinkedList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.UUID;
|
|
|
|
import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
|
|
|
|
/**
|
|
* A durable unit of work.
|
|
*
|
|
* Jobs have {@link Parameters} that describe the conditions upon when you'd like them to run, how
|
|
* 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
|
|
* {@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.
|
|
*/
|
|
public abstract class Job {
|
|
|
|
private static final String TAG = Log.tag(Job.class);
|
|
|
|
private final Parameters parameters;
|
|
|
|
private int runAttempt;
|
|
private long nextRunAttemptTime;
|
|
|
|
private volatile boolean canceled;
|
|
|
|
protected Context context;
|
|
|
|
public Job(@NonNull Parameters parameters) {
|
|
this.parameters = parameters;
|
|
}
|
|
|
|
public final @NonNull String getId() {
|
|
return parameters.getId();
|
|
}
|
|
|
|
public final @NonNull Parameters getParameters() {
|
|
return parameters;
|
|
}
|
|
|
|
public final int getRunAttempt() {
|
|
return runAttempt;
|
|
}
|
|
|
|
public final long getNextRunAttemptTime() {
|
|
return nextRunAttemptTime;
|
|
}
|
|
|
|
public final @Nullable Data getInputData() {
|
|
return parameters.getInputData();
|
|
}
|
|
|
|
public final @NonNull Data requireInputData() {
|
|
return Objects.requireNonNull(parameters.getInputData());
|
|
}
|
|
|
|
/**
|
|
* This is already called by {@link JobController} during job submission, but if you ever run a
|
|
* job without submitting it to the {@link JobManager}, then you'll need to invoke this yourself.
|
|
*/
|
|
public final void setContext(@NonNull Context context) {
|
|
this.context = context;
|
|
}
|
|
|
|
/** Should only be invoked by {@link JobController} */
|
|
final void setRunAttempt(int runAttempt) {
|
|
this.runAttempt = runAttempt;
|
|
}
|
|
|
|
/** Should only be invoked by {@link JobController} */
|
|
final void setNextRunAttemptTime(long nextRunAttemptTime) {
|
|
this.nextRunAttemptTime = nextRunAttemptTime;
|
|
}
|
|
|
|
/** Should only be invoked by {@link JobController} */
|
|
final void cancel() {
|
|
this.canceled = true;
|
|
}
|
|
|
|
@WorkerThread
|
|
final void onSubmit() {
|
|
Log.i(TAG, JobLogger.format(this, "onSubmit()"));
|
|
onAdded();
|
|
}
|
|
|
|
/**
|
|
* @return True if your job has been marked as canceled while it was running, otherwise false.
|
|
* If a job sees that it has been canceled, it should make a best-effort attempt at
|
|
* stopping it's work. This job will have {@link #onFailure()} called after {@link #run()}
|
|
* has finished.
|
|
*/
|
|
public final boolean isCanceled() {
|
|
return canceled;
|
|
}
|
|
|
|
/**
|
|
* Called when the job is first submitted to the {@link JobManager}.
|
|
*/
|
|
@WorkerThread
|
|
public void onAdded() {
|
|
}
|
|
|
|
/**
|
|
* Called after a job has run and its determined that a retry is required.
|
|
*/
|
|
@WorkerThread
|
|
public void onRetry() {
|
|
}
|
|
|
|
/**
|
|
* Serialize your job state so that it can be recreated in the future.
|
|
*/
|
|
public abstract @NonNull Data serialize();
|
|
|
|
/**
|
|
* Returns the key that can be used to find the relevant factory needed to create your job.
|
|
*/
|
|
public abstract @NonNull String getFactoryKey();
|
|
|
|
/**
|
|
* Called to do your actual work.
|
|
*/
|
|
@WorkerThread
|
|
public abstract @NonNull Result run();
|
|
|
|
/**
|
|
* Called when your job has completely failed and will not be run again.
|
|
*/
|
|
@WorkerThread
|
|
public abstract void onFailure();
|
|
|
|
public interface Factory<T extends Job> {
|
|
@NonNull T create(@NonNull Parameters parameters, @NonNull Data data);
|
|
}
|
|
|
|
public static final class Result {
|
|
|
|
private static final int INVALID_BACKOFF = -1;
|
|
|
|
private static final Result SUCCESS_NO_DATA = new Result(ResultType.SUCCESS, null, null, INVALID_BACKOFF);
|
|
private static final Result FAILURE = new Result(ResultType.FAILURE, null, null, INVALID_BACKOFF);
|
|
|
|
private final ResultType resultType;
|
|
private final RuntimeException runtimeException;
|
|
private final Data outputData;
|
|
private final long backoffInterval;
|
|
|
|
private Result(@NonNull ResultType resultType, @Nullable RuntimeException runtimeException, @Nullable Data outputData, long backoffInterval) {
|
|
this.resultType = resultType;
|
|
this.runtimeException = runtimeException;
|
|
this.outputData = outputData;
|
|
this.backoffInterval = backoffInterval;
|
|
}
|
|
|
|
/** Job completed successfully. */
|
|
public static Result success() {
|
|
return SUCCESS_NO_DATA;
|
|
}
|
|
|
|
/** Job completed successfully and wants to provide some output data. */
|
|
public static Result success(@Nullable Data outputData) {
|
|
return new Result(ResultType.SUCCESS, null, outputData, INVALID_BACKOFF);
|
|
}
|
|
|
|
/**
|
|
* Job did not complete successfully, but it can be retried later.
|
|
* @param backoffInterval How long to wait before retrying
|
|
*/
|
|
public static Result retry(long backoffInterval) {
|
|
return new Result(ResultType.RETRY, null, null, backoffInterval);
|
|
}
|
|
|
|
/** Job did not complete successfully and should not be tried again. Dependent jobs will also be failed.*/
|
|
public static Result failure() {
|
|
return FAILURE;
|
|
}
|
|
|
|
/** Same as {@link #failure()}, except the app should also crash with the provided exception. */
|
|
public static Result fatalFailure(@NonNull RuntimeException runtimeException) {
|
|
return new Result(ResultType.FAILURE, runtimeException, null, INVALID_BACKOFF);
|
|
}
|
|
|
|
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
|
public boolean isSuccess() {
|
|
return resultType == ResultType.SUCCESS;
|
|
}
|
|
|
|
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
|
public boolean isRetry() {
|
|
return resultType == ResultType.RETRY;
|
|
}
|
|
|
|
@VisibleForTesting(otherwise = PACKAGE_PRIVATE)
|
|
public boolean isFailure() {
|
|
return resultType == ResultType.FAILURE;
|
|
}
|
|
|
|
@Nullable RuntimeException getException() {
|
|
return runtimeException;
|
|
}
|
|
|
|
@Nullable Data getOutputData() {
|
|
return outputData;
|
|
}
|
|
|
|
long getBackoffInterval() {
|
|
return backoffInterval;
|
|
}
|
|
|
|
@Override
|
|
public @NonNull String toString() {
|
|
switch (resultType) {
|
|
case SUCCESS:
|
|
case RETRY:
|
|
return resultType.toString();
|
|
case FAILURE:
|
|
if (runtimeException == null) {
|
|
return resultType.toString();
|
|
} else {
|
|
return "FATAL_FAILURE";
|
|
}
|
|
}
|
|
|
|
return "UNKNOWN?";
|
|
}
|
|
|
|
private enum ResultType {
|
|
SUCCESS, FAILURE, RETRY
|
|
}
|
|
}
|
|
|
|
public static final class Parameters {
|
|
|
|
public static final String MIGRATION_QUEUE_KEY = "MIGRATION";
|
|
public static final long IMMORTAL = -1;
|
|
public static final int UNLIMITED = -1;
|
|
|
|
private final String id;
|
|
private final long createTime;
|
|
private final long lifespan;
|
|
private final int maxAttempts;
|
|
private final int maxInstancesForFactory;
|
|
private final int maxInstancesForQueue;
|
|
private final String queue;
|
|
private final List<String> constraintKeys;
|
|
private final Data inputData;
|
|
private final boolean memoryOnly;
|
|
|
|
private Parameters(@NonNull String id,
|
|
long createTime,
|
|
long lifespan,
|
|
int maxAttempts,
|
|
int maxInstancesForFactory,
|
|
int maxInstancesForQueue,
|
|
@Nullable String queue,
|
|
@NonNull List<String> constraintKeys,
|
|
@Nullable Data inputData,
|
|
boolean memoryOnly)
|
|
{
|
|
this.id = id;
|
|
this.createTime = createTime;
|
|
this.lifespan = lifespan;
|
|
this.maxAttempts = maxAttempts;
|
|
this.maxInstancesForFactory = maxInstancesForFactory;
|
|
this.maxInstancesForQueue = maxInstancesForQueue;
|
|
this.queue = queue;
|
|
this.constraintKeys = constraintKeys;
|
|
this.inputData = inputData;
|
|
this.memoryOnly = memoryOnly;
|
|
}
|
|
|
|
@NonNull String getId() {
|
|
return id;
|
|
}
|
|
|
|
long getCreateTime() {
|
|
return createTime;
|
|
}
|
|
|
|
long getLifespan() {
|
|
return lifespan;
|
|
}
|
|
|
|
int getMaxAttempts() {
|
|
return maxAttempts;
|
|
}
|
|
|
|
int getMaxInstancesForFactory() {
|
|
return maxInstancesForFactory;
|
|
}
|
|
|
|
int getMaxInstancesForQueue() {
|
|
return maxInstancesForQueue;
|
|
}
|
|
|
|
public @Nullable String getQueue() {
|
|
return queue;
|
|
}
|
|
|
|
@NonNull List<String> getConstraintKeys() {
|
|
return constraintKeys;
|
|
}
|
|
|
|
@Nullable Data getInputData() {
|
|
return inputData;
|
|
}
|
|
|
|
boolean isMemoryOnly() {
|
|
return memoryOnly;
|
|
}
|
|
|
|
public Builder toBuilder() {
|
|
return new Builder(id, createTime, lifespan, maxAttempts, maxInstancesForFactory, maxInstancesForQueue, queue, constraintKeys, inputData, memoryOnly);
|
|
}
|
|
|
|
|
|
public static final class Builder {
|
|
private String id;
|
|
private long createTime;
|
|
private long lifespan;
|
|
private int maxAttempts;
|
|
private int maxInstancesForFactory;
|
|
private int maxInstancesForQueue;
|
|
private String queue;
|
|
private List<String> constraintKeys;
|
|
private Data inputData;
|
|
private boolean memoryOnly;
|
|
|
|
public Builder() {
|
|
this(UUID.randomUUID().toString());
|
|
}
|
|
|
|
Builder(@NonNull String id) {
|
|
this(id, System.currentTimeMillis(), IMMORTAL, 1, UNLIMITED, UNLIMITED, null, new LinkedList<>(), null, false);
|
|
}
|
|
|
|
private Builder(@NonNull String id,
|
|
long createTime,
|
|
long lifespan,
|
|
int maxAttempts,
|
|
int maxInstancesForFactory,
|
|
int maxInstancesForQueue,
|
|
@Nullable String queue,
|
|
@NonNull List<String> constraintKeys,
|
|
@Nullable Data inputData,
|
|
boolean memoryOnly)
|
|
{
|
|
this.id = id;
|
|
this.createTime = createTime;
|
|
this.lifespan = lifespan;
|
|
this.maxAttempts = maxAttempts;
|
|
this.maxInstancesForFactory = maxInstancesForFactory;
|
|
this.maxInstancesForQueue = maxInstancesForQueue;
|
|
this.queue = queue;
|
|
this.constraintKeys = constraintKeys;
|
|
this.inputData = inputData;
|
|
this.memoryOnly = memoryOnly;
|
|
}
|
|
|
|
/** Should only be invoked by {@link JobController} */
|
|
Builder setCreateTime(long createTime) {
|
|
this.createTime = createTime;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify the amount of time this job is allowed to be retried. Defaults to {@link #IMMORTAL}.
|
|
*/
|
|
public @NonNull Builder setLifespan(long lifespan) {
|
|
this.lifespan = lifespan;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify the maximum number of times you want to attempt this job. Defaults to 1.
|
|
*/
|
|
public @NonNull Builder setMaxAttempts(int maxAttempts) {
|
|
this.maxAttempts = maxAttempts;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify the maximum number of instances you'd want of this job at any given time, as
|
|
* determined by the job's factory key. If enqueueing this job would put it over that limit,
|
|
* it will be ignored.
|
|
*
|
|
* This property is ignored if the job is submitted as part of a {@link JobManager.Chain}.
|
|
*
|
|
* Defaults to {@link #UNLIMITED}.
|
|
*/
|
|
public @NonNull Builder setMaxInstancesForFactory(int maxInstancesForFactory) {
|
|
this.maxInstancesForFactory = maxInstancesForFactory;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify the maximum number of instances you'd want of this job at any given time, as
|
|
* determined by the job's factory key and queue key. If enqueueing this job would put it over
|
|
* that limit, it will be ignored.
|
|
*
|
|
* This property is ignored if the job is submitted as part of a {@link JobManager.Chain}, or
|
|
* if the job has no queue key.
|
|
*
|
|
* Defaults to {@link #UNLIMITED}.
|
|
*/
|
|
public @NonNull Builder setMaxInstancesForQueue(int maxInstancesForQueue) {
|
|
this.maxInstancesForQueue = maxInstancesForQueue;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify a string representing a queue. All jobs within the same queue are run in a
|
|
* serialized fashion -- one after the other, in order of insertion. Failure of a job earlier
|
|
* in the queue has no impact on the execution of jobs later in the queue.
|
|
*/
|
|
public @NonNull Builder setQueue(@Nullable String queue) {
|
|
this.queue = queue;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Add a constraint via the key that was used to register its factory in
|
|
* {@link JobManager.Configuration)};
|
|
*/
|
|
public @NonNull Builder addConstraint(@NonNull String constraintKey) {
|
|
constraintKeys.add(constraintKey);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Set constraints via the key that was used to register its factory in
|
|
* {@link JobManager.Configuration)};
|
|
*/
|
|
public @NonNull Builder setConstraints(@NonNull List<String> constraintKeys) {
|
|
this.constraintKeys.clear();
|
|
this.constraintKeys.addAll(constraintKeys);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Specify whether or not you want this job to only live in memory. If true, this job will
|
|
* *not* survive application death. This defaults to false, and should be used with care.
|
|
*
|
|
* Defaults to false.
|
|
*/
|
|
public @NonNull Builder setMemoryOnly(boolean memoryOnly) {
|
|
this.memoryOnly = memoryOnly;
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Sets the input data that will be made availabe to the job when it is run.
|
|
* Should only be set by {@link JobController}.
|
|
*/
|
|
@NonNull Builder setInputData(@Nullable Data inputData) {
|
|
this.inputData = inputData;
|
|
return this;
|
|
}
|
|
|
|
public @NonNull Parameters build() {
|
|
return new Parameters(id, createTime, lifespan, maxAttempts, maxInstancesForFactory, maxInstancesForQueue, queue, constraintKeys, inputData, memoryOnly);
|
|
}
|
|
}
|
|
}
|
|
}
|