mirror of
https://github.com/oxen-io/session-android.git
synced 2025-10-27 22:38:53 +00:00
Schedule jobs with WorkManager.
Should help solve most of our pressing targetSdk=26 migration issues.
This commit is contained in:
@@ -1,138 +1,139 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import android.os.PowerManager;
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.Requirement;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.jobmanager.dependencies.ContextDependent;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirement;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* An abstract class representing a unit of work that can be scheduled with
|
||||
* the JobManager. This should be extended to implement tasks.
|
||||
*/
|
||||
public abstract class Job implements Serializable {
|
||||
import androidx.work.Data;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
private final JobParameters parameters;
|
||||
public abstract class Job extends Worker implements Serializable {
|
||||
|
||||
private transient long persistentId;
|
||||
private transient int runIteration;
|
||||
private transient long lastRunTime;
|
||||
private transient PowerManager.WakeLock wakeLock;
|
||||
private static final long serialVersionUID = -4658540468214421276L;
|
||||
|
||||
public Job(JobParameters parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
private static final String TAG = Job.class.getSimpleName();
|
||||
|
||||
public List<Requirement> getRequirements() {
|
||||
return parameters.getRequirements();
|
||||
}
|
||||
static final String KEY_RETRY_COUNT = "Job_retry_count";
|
||||
static final String KEY_RETRY_UNTIL = "Job_retry_until";
|
||||
static final String KEY_SUBMIT_TIME = "Job_submit_time";
|
||||
static final String KEY_REQUIRES_MASTER_SECRET = "Job_requires_master_secret";
|
||||
static final String KEY_REQUIRES_SQLCIPHER = "Job_requires_sqlcipher";
|
||||
|
||||
public boolean isRequirementsMet() {
|
||||
for (Requirement requirement : parameters.getRequirements()) {
|
||||
if (!requirement.isPresent(this)) return false;
|
||||
}
|
||||
private JobParameters parameters;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return parameters.getGroupId();
|
||||
}
|
||||
|
||||
public boolean isPersistent() {
|
||||
return parameters.isPersistent();
|
||||
}
|
||||
|
||||
public EncryptionKeys getEncryptionKeys() {
|
||||
return parameters.getEncryptionKeys();
|
||||
}
|
||||
|
||||
public void setEncryptionKeys(EncryptionKeys keys) {
|
||||
parameters.setEncryptionKeys(keys);
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return parameters.getRetryCount();
|
||||
}
|
||||
|
||||
public long getRetryUntil() {
|
||||
return parameters.getRetryUntil();
|
||||
}
|
||||
|
||||
public long getLastRunTime() {
|
||||
return lastRunTime;
|
||||
}
|
||||
|
||||
public void resetRunStats() {
|
||||
runIteration = 0;
|
||||
lastRunTime = 0;
|
||||
}
|
||||
|
||||
public void setPersistentId(long persistentId) {
|
||||
this.persistentId = persistentId;
|
||||
}
|
||||
|
||||
public long getPersistentId() {
|
||||
return persistentId;
|
||||
}
|
||||
|
||||
public int getRunIteration() {
|
||||
return runIteration;
|
||||
}
|
||||
|
||||
public boolean needsWakeLock() {
|
||||
return parameters.needsWakeLock();
|
||||
}
|
||||
|
||||
public long getWakeLockTimeout() {
|
||||
return parameters.getWakeLockTimeout();
|
||||
}
|
||||
|
||||
public void setWakeLock(PowerManager.WakeLock wakeLock) {
|
||||
this.wakeLock = wakeLock;
|
||||
}
|
||||
|
||||
public PowerManager.WakeLock getWakeLock() {
|
||||
return this.wakeLock;
|
||||
}
|
||||
|
||||
public void onRetry() {
|
||||
runIteration++;
|
||||
lastRunTime = System.currentTimeMillis();
|
||||
|
||||
for (Requirement requirement : parameters.getRequirements()) {
|
||||
requirement.onRetry(this);
|
||||
}
|
||||
public Job(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
super(context, workerParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a job has been added to the JobManager queue. If it's a persistent job,
|
||||
* the state has been persisted to disk before this method is called.
|
||||
* Invoked when a job is first created in our own codebase.
|
||||
*/
|
||||
public abstract void onAdded();
|
||||
protected Job(@Nullable JobParameters parameters) {
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
Data data = getInputData();
|
||||
|
||||
log("doWork()" + logSuffix());
|
||||
|
||||
ApplicationContext.getInstance(getApplicationContext()).ensureInitialized();
|
||||
ApplicationContext.getInstance(getApplicationContext()).injectDependencies(this);
|
||||
|
||||
if (this instanceof ContextDependent) {
|
||||
((ContextDependent)this).setContext(getApplicationContext());
|
||||
}
|
||||
|
||||
initialize(new SafeData(data));
|
||||
|
||||
try {
|
||||
if (withinRetryLimits(data)) {
|
||||
if (requirementsMet(data)) {
|
||||
onRun();
|
||||
log("Successfully completed." + logSuffix());
|
||||
return Result.SUCCESS;
|
||||
} else {
|
||||
log("Retrying due to unmet requirements." + logSuffix());
|
||||
return retry();
|
||||
}
|
||||
} else {
|
||||
warn("Failing after hitting the retry limit." + logSuffix());
|
||||
return cancel();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (onShouldRetry(e)) {
|
||||
log("Retrying after a retryable exception." + logSuffix());
|
||||
return retry();
|
||||
}
|
||||
warn("Failing due to an exception." + logSuffix(), e);
|
||||
return cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStopped(boolean cancelled) {
|
||||
if (cancelled) {
|
||||
warn("onStopped() with cancellation signal." + logSuffix());
|
||||
onCanceled();
|
||||
}
|
||||
}
|
||||
|
||||
final void onSubmit(UUID id) {
|
||||
log(id, "onSubmit()");
|
||||
onAdded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after a run has finished and we've determined a retry is required, but before the next
|
||||
* attempt is run.
|
||||
*/
|
||||
protected void onRetry() { }
|
||||
|
||||
/**
|
||||
* Called after a job has been added to the JobManager queue. Invoked off the main thread, so its
|
||||
* safe to do longer-running work. However, work should finish relatively quickly, as it will
|
||||
* block the submission of future tasks.
|
||||
*/
|
||||
protected void onAdded() { }
|
||||
|
||||
/**
|
||||
* All instance state needs to be persisted in the provided {@link Data.Builder} so that it can
|
||||
* be restored in {@link #initialize(SafeData)}.
|
||||
* @param dataBuilder The builder where you put your state.
|
||||
* @return The result of {@code dataBuilder.build()}.
|
||||
*/
|
||||
protected abstract @NonNull Data serialize(@NonNull Data.Builder dataBuilder);
|
||||
|
||||
/**
|
||||
* Restore all of your instance state from the provided {@link Data}. It should contain all of
|
||||
* the data put in during {@link #serialize(Data.Builder)}.
|
||||
* @param data Where your data is stored.
|
||||
*/
|
||||
protected abstract void initialize(@NonNull SafeData data);
|
||||
|
||||
/**
|
||||
* Called to actually execute the job.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected abstract void onRun() throws Exception;
|
||||
public abstract void onRun() throws Exception;
|
||||
|
||||
/**
|
||||
* Called if a job fails to run (onShouldRetry returned false, or the number of retries exceeded
|
||||
* the job's configured retry count.
|
||||
*/
|
||||
protected abstract void onCanceled();
|
||||
|
||||
/**
|
||||
* If onRun() throws an exception, this method will be called to determine whether the
|
||||
@@ -141,14 +142,69 @@ public abstract class Job implements Serializable {
|
||||
* @param exception The exception onRun() threw.
|
||||
* @return true if onRun() should be called again, false otherwise.
|
||||
*/
|
||||
public abstract boolean onShouldRetry(Exception exception);
|
||||
protected abstract boolean onShouldRetry(Exception exception);
|
||||
|
||||
/**
|
||||
* Called if a job fails to run (onShouldRetry returned false, or the number of retries exceeded
|
||||
* the job's configured retry count.
|
||||
*/
|
||||
public abstract void onCanceled();
|
||||
@Nullable JobParameters getJobParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private Result retry() {
|
||||
onRetry();
|
||||
return Result.RETRY;
|
||||
}
|
||||
|
||||
private Result cancel() {
|
||||
onCanceled();
|
||||
return Result.SUCCESS;
|
||||
}
|
||||
|
||||
private boolean requirementsMet(Data data) {
|
||||
boolean met = true;
|
||||
|
||||
if (data.getBoolean(KEY_REQUIRES_MASTER_SECRET, false)) {
|
||||
met &= new MasterSecretRequirement(getApplicationContext()).isPresent();
|
||||
}
|
||||
|
||||
if (data.getBoolean(KEY_REQUIRES_SQLCIPHER, false)) {
|
||||
met &= new SqlCipherMigrationRequirement(getApplicationContext()).isPresent();
|
||||
}
|
||||
|
||||
return met;
|
||||
}
|
||||
|
||||
private boolean withinRetryLimits(Data data) {
|
||||
int retryCount = data.getInt(KEY_RETRY_COUNT, 0);
|
||||
long retryUntil = data.getLong(KEY_RETRY_UNTIL, 0);
|
||||
|
||||
if (retryCount > 0) {
|
||||
return getRunAttemptCount() <= retryCount;
|
||||
}
|
||||
|
||||
return System.currentTimeMillis() < retryUntil;
|
||||
}
|
||||
|
||||
private void log(@NonNull String message) {
|
||||
log(getId(), message);
|
||||
}
|
||||
|
||||
private void log(@NonNull UUID id, @NonNull String message) {
|
||||
Log.i(TAG, buildLog(id, message));
|
||||
}
|
||||
|
||||
private void warn(@NonNull String message) {
|
||||
warn(message, null);
|
||||
}
|
||||
|
||||
private void warn(@NonNull String message, @Nullable Throwable t) {
|
||||
Log.w(TAG, buildLog(getId(), message), t);
|
||||
}
|
||||
|
||||
private String buildLog(@NonNull UUID id, @NonNull String message) {
|
||||
return "[" + id + "] " + getClass().getSimpleName() + " :: " + message;
|
||||
}
|
||||
|
||||
private String logSuffix() {
|
||||
long timeSinceSubmission = System.currentTimeMillis() - getInputData().getLong(KEY_SUBMIT_TIME, 0);
|
||||
return " (Time since submission: " + timeSinceSubmission + " ms, Run attempt: " + getRunAttemptCount() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.PersistentStorage;
|
||||
|
||||
class JobConsumer extends Thread {
|
||||
|
||||
private static final String TAG = JobConsumer.class.getSimpleName();
|
||||
|
||||
enum JobResult {
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
DEFERRED
|
||||
}
|
||||
|
||||
private final JobQueue jobQueue;
|
||||
private final PersistentStorage persistentStorage;
|
||||
|
||||
public JobConsumer(String name, JobQueue jobQueue, PersistentStorage persistentStorage) {
|
||||
super(name);
|
||||
this.jobQueue = jobQueue;
|
||||
this.persistentStorage = persistentStorage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
Job job = jobQueue.getNext();
|
||||
JobResult result = runJob(job);
|
||||
|
||||
if (result == JobResult.DEFERRED) {
|
||||
jobQueue.push(job);
|
||||
} else {
|
||||
if (result == JobResult.FAILURE) {
|
||||
job.onCanceled();
|
||||
}
|
||||
|
||||
if (job.isPersistent()) {
|
||||
persistentStorage.remove(job.getPersistentId());
|
||||
}
|
||||
|
||||
if (job.getWakeLock() != null && job.getWakeLockTimeout() == 0) {
|
||||
job.getWakeLock().release();
|
||||
}
|
||||
|
||||
if (job.getGroupId() != null) {
|
||||
jobQueue.setGroupIdAvailable(job.getGroupId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private JobResult runJob(Job job) {
|
||||
while (canRetry(job)) {
|
||||
try {
|
||||
job.onRun();
|
||||
return JobResult.SUCCESS;
|
||||
} catch (Exception exception) {
|
||||
Log.w(TAG, exception);
|
||||
if (exception instanceof RuntimeException) {
|
||||
throw (RuntimeException)exception;
|
||||
} else if (!job.onShouldRetry(exception)) {
|
||||
return JobResult.FAILURE;
|
||||
}
|
||||
|
||||
job.onRetry();
|
||||
if (!job.isRequirementsMet()) {
|
||||
return JobResult.DEFERRED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JobResult.FAILURE;
|
||||
}
|
||||
|
||||
private boolean canRetry(@NonNull Job job) {
|
||||
if (job.getRetryCount() > 0) {
|
||||
return job.getRunIteration() < job.getRetryCount();
|
||||
}
|
||||
return System.currentTimeMillis() < job.getRetryUntil();
|
||||
}
|
||||
}
|
||||
@@ -1,252 +1,69 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.PowerManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.dependencies.AggregateDependencyInjector;
|
||||
import org.thoughtcrime.securesms.jobmanager.dependencies.DependencyInjector;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.JobSerializer;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.PersistentStorage;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.RequirementListener;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.RequirementProvider;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A JobManager allows you to enqueue {@link org.thoughtcrime.securesms.jobmanager.Job} tasks
|
||||
* that are executed once a Job's {@link org.thoughtcrime.securesms.jobmanager.requirements.Requirement}s
|
||||
* are met.
|
||||
*/
|
||||
public class JobManager implements RequirementListener {
|
||||
import androidx.work.BackoffPolicy;
|
||||
import androidx.work.Constraints;
|
||||
import androidx.work.Data;
|
||||
import androidx.work.ExistingWorkPolicy;
|
||||
import androidx.work.NetworkType;
|
||||
import androidx.work.OneTimeWorkRequest;
|
||||
import androidx.work.WorkManager;
|
||||
|
||||
private final JobQueue jobQueue = new JobQueue();
|
||||
private final Executor eventExecutor = Executors.newSingleThreadExecutor();
|
||||
private final AtomicBoolean hasLoadedEncrypted = new AtomicBoolean(false);
|
||||
public class JobManager {
|
||||
|
||||
private final Context context;
|
||||
private final PersistentStorage persistentStorage;
|
||||
private final List<RequirementProvider> requirementProviders;
|
||||
private final AggregateDependencyInjector dependencyInjector;
|
||||
private static final Constraints NETWORK_CONSTRAINT = new Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build();
|
||||
|
||||
private JobManager(Context context, String name,
|
||||
List<RequirementProvider> requirementProviders,
|
||||
DependencyInjector dependencyInjector,
|
||||
JobSerializer jobSerializer, int consumers)
|
||||
{
|
||||
this.context = context;
|
||||
this.dependencyInjector = new AggregateDependencyInjector(dependencyInjector);
|
||||
this.persistentStorage = new PersistentStorage(context, name, jobSerializer, this.dependencyInjector);
|
||||
this.requirementProviders = requirementProviders;
|
||||
private final Executor executor = Executors.newSingleThreadExecutor();
|
||||
|
||||
eventExecutor.execute(new LoadTask(null));
|
||||
private final WorkManager workManager;
|
||||
|
||||
if (requirementProviders != null && !requirementProviders.isEmpty()) {
|
||||
for (RequirementProvider provider : requirementProviders) {
|
||||
provider.setListener(this);
|
||||
public JobManager(@NonNull WorkManager workManager) {
|
||||
this.workManager = workManager;
|
||||
}
|
||||
|
||||
public void add(Job job) {
|
||||
executor.execute(() -> {
|
||||
workManager.synchronous().pruneWorkSync();
|
||||
|
||||
JobParameters jobParameters = job.getJobParameters();
|
||||
|
||||
if (jobParameters == null) {
|
||||
throw new IllegalStateException("Jobs must have JobParameters at this stage.");
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0;i<consumers;i++) {
|
||||
new JobConsumer("JobConsumer-" + i, jobQueue, persistentStorage).start();
|
||||
}
|
||||
}
|
||||
Data.Builder dataBuilder = new Data.Builder().putInt(Job.KEY_RETRY_COUNT, jobParameters.getRetryCount())
|
||||
.putLong(Job.KEY_RETRY_UNTIL, jobParameters.getRetryUntil())
|
||||
.putLong(Job.KEY_SUBMIT_TIME, System.currentTimeMillis())
|
||||
.putBoolean(Job.KEY_REQUIRES_MASTER_SECRET, jobParameters.requiresMasterSecret())
|
||||
.putBoolean(Job.KEY_REQUIRES_SQLCIPHER, jobParameters.requiresSqlCipher());
|
||||
Data data = job.serialize(dataBuilder);
|
||||
|
||||
/**
|
||||
* @param context An Android {@link android.content.Context}.
|
||||
* @return a {@link org.thoughtcrime.securesms.jobmanager.JobManager.Builder} used to construct a JobManager.
|
||||
*/
|
||||
public static Builder newBuilder(Context context) {
|
||||
return new Builder(context);
|
||||
}
|
||||
OneTimeWorkRequest.Builder requestBuilder = new OneTimeWorkRequest.Builder(job.getClass())
|
||||
.setInputData(data)
|
||||
.setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS);
|
||||
|
||||
public void setEncryptionKeys(EncryptionKeys keys) {
|
||||
if (hasLoadedEncrypted.compareAndSet(false, true)) {
|
||||
eventExecutor.execute(new LoadTask(keys));
|
||||
}
|
||||
}
|
||||
if (jobParameters.requiresNetwork()) {
|
||||
requestBuilder.setConstraints(NETWORK_CONSTRAINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a {@link org.thoughtcrime.securesms.jobmanager.Job} to be executed.
|
||||
*
|
||||
* @param job The Job to be executed.
|
||||
*/
|
||||
public void add(final Job job) {
|
||||
if (job.needsWakeLock()) {
|
||||
job.setWakeLock(acquireWakeLock(context, job.toString(), job.getWakeLockTimeout()));
|
||||
}
|
||||
OneTimeWorkRequest request = requestBuilder.build();
|
||||
|
||||
eventExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
if (job.isPersistent()) {
|
||||
persistentStorage.store(job);
|
||||
}
|
||||
job.onSubmit(request.getId());
|
||||
|
||||
dependencyInjector.injectDependencies(context, job);
|
||||
|
||||
job.onAdded();
|
||||
jobQueue.add(job);
|
||||
} catch (IOException e) {
|
||||
Log.w("JobManager", e);
|
||||
job.onCanceled();
|
||||
}
|
||||
String groupId = jobParameters.getGroupId();
|
||||
if (groupId != null) {
|
||||
ExistingWorkPolicy policy = jobParameters.shouldIgnoreDuplicates() ? ExistingWorkPolicy.KEEP : ExistingWorkPolicy.APPEND;
|
||||
workManager.beginUniqueWork(groupId, policy, request).enqueue();
|
||||
} else {
|
||||
workManager.beginWith(request).enqueue();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequirementStatusChanged() {
|
||||
eventExecutor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
jobQueue.onRequirementStatusChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private PowerManager.WakeLock acquireWakeLock(Context context, String name, long timeout) {
|
||||
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
|
||||
|
||||
if (timeout == 0) wakeLock.acquire();
|
||||
else wakeLock.acquire(timeout);
|
||||
|
||||
return wakeLock;
|
||||
}
|
||||
|
||||
private class LoadTask implements Runnable {
|
||||
|
||||
private final EncryptionKeys keys;
|
||||
|
||||
public LoadTask(EncryptionKeys keys) {
|
||||
this.keys = keys;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<Job> pendingJobs;
|
||||
|
||||
if (keys == null) pendingJobs = persistentStorage.getAllUnencrypted();
|
||||
else pendingJobs = persistentStorage.getAllEncrypted(keys);
|
||||
|
||||
jobQueue.addAll(pendingJobs);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final Context context;
|
||||
private String name;
|
||||
private List<RequirementProvider> requirementProviders;
|
||||
private DependencyInjector dependencyInjector;
|
||||
private JobSerializer jobSerializer;
|
||||
private int consumerThreads;
|
||||
|
||||
Builder(Context context) {
|
||||
this.context = context;
|
||||
this.consumerThreads = 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* A name for the {@link org.thoughtcrime.securesms.jobmanager.JobManager}. This is a required parameter,
|
||||
* and is linked to the durable queue used by persistent jobs.
|
||||
*
|
||||
* @param name The name for the JobManager to build.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder withName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link org.thoughtcrime.securesms.jobmanager.requirements.RequirementProvider}s to register with this
|
||||
* JobManager. Optional. Each {@link org.thoughtcrime.securesms.jobmanager.requirements.Requirement} an
|
||||
* enqueued Job depends on should have a matching RequirementProvider registered here.
|
||||
*
|
||||
* @param requirementProviders The RequirementProviders
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder withRequirementProviders(RequirementProvider... requirementProviders) {
|
||||
this.requirementProviders = Arrays.asList(requirementProviders);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link org.thoughtcrime.securesms.jobmanager.dependencies.DependencyInjector} to use for injecting
|
||||
* dependencies into {@link Job}s. Optional. Injection occurs just before a Job's onAdded() callback, or
|
||||
* after deserializing a persistent job.
|
||||
*
|
||||
* @param dependencyInjector The injector to use.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder withDependencyInjector(DependencyInjector dependencyInjector) {
|
||||
this.dependencyInjector = dependencyInjector;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link org.thoughtcrime.securesms.jobmanager.persistence.JobSerializer} to use for persistent Jobs.
|
||||
* Required if persistent Jobs are used.
|
||||
*
|
||||
* @param jobSerializer The serializer to use.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder withJobSerializer(JobSerializer jobSerializer) {
|
||||
this.jobSerializer = jobSerializer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of threads dedicated to consuming Jobs from the queue and executing them.
|
||||
*
|
||||
* @param consumerThreads The number of threads.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder withConsumerThreads(int consumerThreads) {
|
||||
this.consumerThreads = consumerThreads;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A constructed JobManager.
|
||||
*/
|
||||
public JobManager build() {
|
||||
if (name == null) {
|
||||
throw new IllegalArgumentException("You must specify a name!");
|
||||
}
|
||||
|
||||
if (requirementProviders == null) {
|
||||
requirementProviders = new LinkedList<>();
|
||||
}
|
||||
|
||||
return new JobManager(context, name, requirementProviders,
|
||||
dependencyInjector, jobSerializer,
|
||||
consumerThreads);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,12 +16,16 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkBackoffRequirement;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.Requirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.NetworkOrServiceRequirement;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirement;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* The set of parameters that describe a {@link org.thoughtcrime.securesms.jobmanager.Job}.
|
||||
@@ -30,46 +34,86 @@ public class JobParameters implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 4880456378402584584L;
|
||||
|
||||
private transient EncryptionKeys encryptionKeys;
|
||||
|
||||
private final List<Requirement> requirements;
|
||||
private final boolean isPersistent;
|
||||
private final boolean requiresNetwork;
|
||||
private final boolean requiresMasterSecret;
|
||||
private final boolean requiresSqlCipher;
|
||||
private final int retryCount;
|
||||
private final long retryUntil;
|
||||
private final String groupId;
|
||||
private final boolean wakeLock;
|
||||
private final long wakeLockTimeout;
|
||||
private final boolean ignoreDuplicates;
|
||||
|
||||
private JobParameters(List<Requirement> requirements,
|
||||
boolean isPersistent, String groupId,
|
||||
EncryptionKeys encryptionKeys,
|
||||
int retryCount, long retryUntil, boolean wakeLock,
|
||||
long wakeLockTimeout)
|
||||
private JobParameters(String groupId,
|
||||
boolean ignoreDuplicates,
|
||||
boolean requiresNetwork,
|
||||
boolean requiresMasterSecret,
|
||||
boolean requiresSqlCipher,
|
||||
int retryCount,
|
||||
long retryUntil)
|
||||
{
|
||||
this.requirements = requirements;
|
||||
this.isPersistent = isPersistent;
|
||||
this.groupId = groupId;
|
||||
this.encryptionKeys = encryptionKeys;
|
||||
this.retryCount = retryCount;
|
||||
this.retryUntil = retryUntil;
|
||||
this.wakeLock = wakeLock;
|
||||
this.wakeLockTimeout = wakeLockTimeout;
|
||||
this.groupId = groupId;
|
||||
this.ignoreDuplicates = ignoreDuplicates;
|
||||
this.requirements = Collections.emptyList();
|
||||
this.requiresNetwork = requiresNetwork;
|
||||
this.requiresMasterSecret = requiresMasterSecret;
|
||||
this.requiresSqlCipher = requiresSqlCipher;
|
||||
this.retryCount = retryCount;
|
||||
this.retryUntil = retryUntil;
|
||||
}
|
||||
|
||||
public List<Requirement> getRequirements() {
|
||||
return requirements;
|
||||
public boolean shouldIgnoreDuplicates() {
|
||||
return ignoreDuplicates;
|
||||
}
|
||||
|
||||
public boolean isPersistent() {
|
||||
return isPersistent;
|
||||
public boolean requiresNetwork() {
|
||||
return requiresNetwork || hasNetworkRequirement(requirements);
|
||||
}
|
||||
|
||||
public EncryptionKeys getEncryptionKeys() {
|
||||
return encryptionKeys;
|
||||
public boolean requiresMasterSecret() {
|
||||
return requiresMasterSecret || hasMasterSecretRequirement(requirements);
|
||||
}
|
||||
|
||||
public void setEncryptionKeys(EncryptionKeys encryptionKeys) {
|
||||
this.encryptionKeys = encryptionKeys;
|
||||
public boolean requiresSqlCipher() {
|
||||
return requiresSqlCipher || hasSqlCipherRequirement(requirements);
|
||||
}
|
||||
|
||||
private boolean hasNetworkRequirement(List<Requirement> requirements) {
|
||||
if (requirements == null || requirements.size() == 0) return false;
|
||||
|
||||
for (Requirement requirement : requirements) {
|
||||
if (requirement instanceof NetworkRequirement ||
|
||||
requirement instanceof NetworkOrServiceRequirement ||
|
||||
requirement instanceof NetworkBackoffRequirement)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasMasterSecretRequirement(List<Requirement> requirements) {
|
||||
if (requirements == null || requirements.size() == 0) return false;
|
||||
|
||||
for (Requirement requirement : requirements) {
|
||||
if (requirement instanceof MasterSecretRequirement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasSqlCipherRequirement(List<Requirement> requirements) {
|
||||
if (requirements == null || requirements.size() == 0) return false;
|
||||
|
||||
for (Requirement requirement : requirements) {
|
||||
if (requirement instanceof SqlCipherMigrationRequirement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
@@ -91,52 +135,29 @@ public class JobParameters implements Serializable {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public boolean needsWakeLock() {
|
||||
return wakeLock;
|
||||
}
|
||||
|
||||
public long getWakeLockTimeout() {
|
||||
return wakeLockTimeout;
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private List<Requirement> requirements = new LinkedList<>();
|
||||
private boolean isPersistent = false;
|
||||
private EncryptionKeys encryptionKeys = null;
|
||||
private int retryCount = 100;
|
||||
private long retryDuration = 0;
|
||||
private String groupId = null;
|
||||
private boolean wakeLock = false;
|
||||
private long wakeLockTimeout = 0;
|
||||
private int retryCount = 100;
|
||||
private long retryDuration = 0;
|
||||
private String groupId = null;
|
||||
private boolean ignoreDuplicates = false;
|
||||
private boolean requiresNetwork = false;
|
||||
private boolean requiresSqlCipher = false;
|
||||
private boolean requiresMasterSecret = false;
|
||||
|
||||
/**
|
||||
* Specify a {@link org.thoughtcrime.securesms.jobmanager.requirements.Requirement }that must be met
|
||||
* before the Job is executed. May be called multiple times to register multiple requirements.
|
||||
* @param requirement The Requirement that must be met.
|
||||
* @return the builder.
|
||||
*/
|
||||
public Builder withRequirement(Requirement requirement) {
|
||||
this.requirements.add(requirement);
|
||||
public Builder withNetworkRequirement() {
|
||||
requiresNetwork = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the Job should be durably persisted to disk, so that it remains in the queue
|
||||
* across application restarts.
|
||||
* @return The builder.
|
||||
*/
|
||||
public Builder withPersistence() {
|
||||
this.isPersistent = true;
|
||||
@Deprecated
|
||||
public Builder withMasterSecretRequirement() {
|
||||
requiresMasterSecret = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify that the job should use encryption when durably persisted to disk.
|
||||
* @param encryptionKeys The keys to encrypt the serialized job with before persisting.
|
||||
* @return the builder.
|
||||
*/
|
||||
public Builder withEncryption(EncryptionKeys encryptionKeys) {
|
||||
this.encryptionKeys = encryptionKeys;
|
||||
@Deprecated
|
||||
public Builder withSqlCipherRequirement() {
|
||||
requiresSqlCipher = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -153,6 +174,11 @@ public class JobParameters implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify for how long we should keep retrying this job. Ignored if retryCount is set.
|
||||
* @param duration The duration (in ms) for how long we should keep retrying this job for.
|
||||
* @return the builder
|
||||
*/
|
||||
public Builder withRetryDuration(long duration) {
|
||||
this.retryDuration = duration;
|
||||
this.retryCount = 0;
|
||||
@@ -172,35 +198,25 @@ public class JobParameters implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether this job should hold a wake lock.
|
||||
* If true, only one job with this groupId can be active at a time. If a job with the same
|
||||
* groupId is already running, then subsequent jobs will be ignored silently. Only has an effect
|
||||
* if a groupId has been specified via {@link #withGroupId(String)}.
|
||||
* <p />
|
||||
* Defaults to false.
|
||||
*
|
||||
* @param needsWakeLock If set, this job will acquire a wakelock on add(), and hold it until
|
||||
* run() completes, or cancel().
|
||||
* @param timeout Specify a timeout for the wakelock. A timeout of
|
||||
* 0 will result in no timeout.
|
||||
*
|
||||
* @return the builder.
|
||||
* @param ignoreDuplicates Whether to ignore duplicates.
|
||||
* @return the builder
|
||||
*/
|
||||
public Builder withWakeLock(boolean needsWakeLock, long timeout, TimeUnit unit) {
|
||||
this.wakeLock = needsWakeLock;
|
||||
this.wakeLockTimeout = unit.toMillis(timeout);
|
||||
public Builder withDuplicatesIgnored(boolean ignoreDuplicates) {
|
||||
this.ignoreDuplicates = ignoreDuplicates;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether this job should hold a wake lock.
|
||||
*
|
||||
* @return the builder.
|
||||
*/
|
||||
public Builder withWakeLock(boolean needsWakeLock) {
|
||||
return withWakeLock(needsWakeLock, 0, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the JobParameters instance that describes a Job.
|
||||
*/
|
||||
public JobParameters create() {
|
||||
return new JobParameters(requirements, isPersistent, groupId, encryptionKeys, retryCount, System.currentTimeMillis() + retryDuration, wakeLock, wakeLockTimeout);
|
||||
return new JobParameters(groupId, ignoreDuplicates, requiresNetwork, requiresMasterSecret, requiresSqlCipher, retryCount, System.currentTimeMillis() + retryDuration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open Whisper Systems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Map;
|
||||
|
||||
class JobQueue {
|
||||
|
||||
private final Map<String, Job> activeGroupIds = new HashMap<>();
|
||||
private final LinkedList<Job> jobQueue = new LinkedList<>();
|
||||
|
||||
synchronized void onRequirementStatusChanged() {
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
synchronized void add(Job job) {
|
||||
jobQueue.add(job);
|
||||
processJobAddition(job);
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
synchronized void addAll(List<Job> jobs) {
|
||||
jobQueue.addAll(jobs);
|
||||
|
||||
for (Job job : jobs) {
|
||||
processJobAddition(job);
|
||||
}
|
||||
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
private void processJobAddition(@NonNull Job job) {
|
||||
if (isJobActive(job) && isGroupIdAvailable(job)) {
|
||||
setGroupIdUnavailable(job);
|
||||
} else if (!isGroupIdAvailable(job)) {
|
||||
Job blockingJob = activeGroupIds.get(job.getGroupId());
|
||||
blockingJob.resetRunStats();
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void push(Job job) {
|
||||
jobQueue.addFirst(job);
|
||||
}
|
||||
|
||||
synchronized Job getNext() {
|
||||
try {
|
||||
Job nextAvailableJob;
|
||||
|
||||
while ((nextAvailableJob = getNextAvailableJob()) == null) {
|
||||
wait();
|
||||
}
|
||||
|
||||
return nextAvailableJob;
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void setGroupIdAvailable(String groupId) {
|
||||
if (groupId != null) {
|
||||
activeGroupIds.remove(groupId);
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private Job getNextAvailableJob() {
|
||||
if (jobQueue.isEmpty()) return null;
|
||||
|
||||
ListIterator<Job> iterator = jobQueue.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
Job job = iterator.next();
|
||||
|
||||
if (job.isRequirementsMet() && isGroupIdAvailable(job)) {
|
||||
iterator.remove();
|
||||
setGroupIdUnavailable(job);
|
||||
return job;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isJobActive(@NonNull Job job) {
|
||||
return job.getRetryUntil() > 0 && job.getRunIteration() > 0;
|
||||
}
|
||||
|
||||
private boolean isGroupIdAvailable(@NonNull Job requester) {
|
||||
String groupId = requester.getGroupId();
|
||||
return groupId == null || !activeGroupIds.containsKey(groupId) || activeGroupIds.get(groupId).equals(requester);
|
||||
}
|
||||
|
||||
private void setGroupIdUnavailable(@NonNull Job job) {
|
||||
String groupId = job.getGroupId();
|
||||
if (groupId != null) {
|
||||
activeGroupIds.put(groupId, job);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
src/org/thoughtcrime/securesms/jobmanager/SafeData.java
Normal file
80
src/org/thoughtcrime/securesms/jobmanager/SafeData.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package org.thoughtcrime.securesms.jobmanager;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import androidx.work.Data;
|
||||
|
||||
/**
|
||||
* A wrapper around {@link Data} that does its best to throw an exception whenever a key isn't
|
||||
* present in the {@link Data} object.
|
||||
*/
|
||||
public class SafeData {
|
||||
|
||||
private final static int INVALID_INT = Integer.MIN_VALUE;
|
||||
private final static long INVALID_LONG = Long.MIN_VALUE;
|
||||
|
||||
private final Data data;
|
||||
|
||||
public SafeData(@NonNull Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public int getInt(@NonNull String key) {
|
||||
int value = data.getInt(key, INVALID_INT);
|
||||
|
||||
if (value == INVALID_INT) {
|
||||
throw new IllegalStateException("Missing key: " + key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public long getLong(@NonNull String key) {
|
||||
long value = data.getLong(key, INVALID_LONG);
|
||||
|
||||
if (value == INVALID_LONG) {
|
||||
throw new IllegalStateException("Missing key: " + key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public @NonNull String getString(@NonNull String key) {
|
||||
String value = data.getString(key);
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("Missing key: " + key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public @Nullable String getNullableString(@NonNull String key) {
|
||||
return data.getString(key);
|
||||
}
|
||||
|
||||
public @NonNull String[] getStringArray(@NonNull String key) {
|
||||
String[] value = data.getStringArray(key);
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("Missing key: " + key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public @NonNull long[] getLongArray(@NonNull String key) {
|
||||
long[] value = data.getLongArray(key);
|
||||
|
||||
if (value == null) {
|
||||
throw new IllegalStateException("Missing key: " + key);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean getBoolean(@NonNull String key, boolean defaultValue) {
|
||||
return data.getBoolean(key, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobmanager.dependencies;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.requirements.Requirement;
|
||||
|
||||
public class AggregateDependencyInjector {
|
||||
|
||||
private final DependencyInjector dependencyInjector;
|
||||
|
||||
public AggregateDependencyInjector(DependencyInjector dependencyInjector) {
|
||||
this.dependencyInjector = dependencyInjector;
|
||||
}
|
||||
|
||||
public void injectDependencies(Context context, Job job) {
|
||||
if (job instanceof ContextDependent) {
|
||||
((ContextDependent)job).setContext(context);
|
||||
}
|
||||
|
||||
for (Requirement requirement : job.getRequirements()) {
|
||||
if (requirement instanceof ContextDependent) {
|
||||
((ContextDependent)requirement).setContext(context);
|
||||
}
|
||||
}
|
||||
|
||||
if (dependencyInjector != null) {
|
||||
dependencyInjector.injectDependencies(job);
|
||||
|
||||
for (Requirement requirement : job.getRequirements()) {
|
||||
dependencyInjector.injectDependencies(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.jobmanager.persistence;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@@ -24,7 +23,6 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import org.thoughtcrime.securesms.jobmanager.EncryptionKeys;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.jobmanager.dependencies.AggregateDependencyInjector;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -43,38 +41,18 @@ public class PersistentStorage {
|
||||
private static final String DATABASE_CREATE = String.format("CREATE TABLE %s (%s INTEGER PRIMARY KEY, %s TEXT NOT NULL, %s INTEGER DEFAULT 0);",
|
||||
TABLE_NAME, ID, ITEM, ENCRYPTED);
|
||||
|
||||
private final Context context;
|
||||
private final DatabaseHelper databaseHelper;
|
||||
private final JobSerializer jobSerializer;
|
||||
private final AggregateDependencyInjector dependencyInjector;
|
||||
private final DatabaseHelper databaseHelper;
|
||||
private final JobSerializer jobSerializer;
|
||||
|
||||
public PersistentStorage(Context context, String name,
|
||||
JobSerializer serializer,
|
||||
AggregateDependencyInjector dependencyInjector)
|
||||
{
|
||||
public PersistentStorage(Context context, String name, JobSerializer serializer) {
|
||||
this.databaseHelper = new DatabaseHelper(context, "_jobqueue-" + name);
|
||||
this.context = context;
|
||||
this.jobSerializer = serializer;
|
||||
this.dependencyInjector = dependencyInjector;
|
||||
}
|
||||
|
||||
public void store(Job job) throws IOException {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(ITEM, jobSerializer.serialize(job));
|
||||
contentValues.put(ENCRYPTED, job.getEncryptionKeys() != null);
|
||||
|
||||
long id = databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
|
||||
job.setPersistentId(id);
|
||||
}
|
||||
|
||||
public List<Job> getAllUnencrypted() {
|
||||
return getJobs(null, ENCRYPTED + " = 0");
|
||||
}
|
||||
|
||||
public List<Job> getAllEncrypted(EncryptionKeys keys) {
|
||||
return getJobs(keys, ENCRYPTED + " = 1");
|
||||
}
|
||||
|
||||
private List<Job> getJobs(EncryptionKeys keys, String where) {
|
||||
List<Job> results = new LinkedList<>();
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
@@ -90,11 +68,6 @@ public class PersistentStorage {
|
||||
|
||||
try{
|
||||
Job job = jobSerializer.deserialize(keys, encrypted, item);
|
||||
|
||||
job.setPersistentId(id);
|
||||
job.setEncryptionKeys(keys);
|
||||
dependencyInjector.injectDependencies(context, job);
|
||||
|
||||
results.add(job);
|
||||
} catch (IOException e) {
|
||||
Log.w("PersistentStore", e);
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobmanager.requirements;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BackoffReceiver extends BroadcastReceiver {
|
||||
|
||||
private static final String TAG = BackoffReceiver.class.getSimpleName();
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "Received an alarm to retry a job with ID: " + intent.getAction());
|
||||
ApplicationContext.getInstance(context).getJobManager().onRequirementStatusChanged();
|
||||
}
|
||||
|
||||
public static void setUniqueAlarm(@NonNull Context context, long time) {
|
||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
Intent intent = new Intent(context, BackoffReceiver.class);
|
||||
|
||||
intent.setAction(BuildConfig.APPLICATION_ID + UUID.randomUUID().toString());
|
||||
alarmManager.set(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(context, 0, intent, 0));
|
||||
|
||||
Log.i(TAG, "Set an alarm to retry a job in " + (time - System.currentTimeMillis()) + " ms with ID: " + intent.getAction());
|
||||
}
|
||||
}
|
||||
@@ -31,15 +31,6 @@ public class NetworkBackoffRequirement implements Requirement, ContextDependent
|
||||
|
||||
@Override
|
||||
public void onRetry(@NonNull Job job) {
|
||||
Log.i(TAG, "onRetry()");
|
||||
|
||||
if (!(new NetworkRequirement(context).isPresent())) {
|
||||
Log.i(TAG, "No network. Resetting run stats.");
|
||||
job.resetRunStats();
|
||||
return;
|
||||
}
|
||||
|
||||
BackoffReceiver.setUniqueAlarm(context, NetworkBackoffRequirement.calculateNextRunTime(job));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,9 +39,6 @@ public class NetworkBackoffRequirement implements Requirement, ContextDependent
|
||||
}
|
||||
|
||||
private static long calculateNextRunTime(@NonNull Job job) {
|
||||
long targetTime = job.getLastRunTime() + (long) (Math.pow(2, job.getRunIteration() - 1) * 1000);
|
||||
long furthestTime = System.currentTimeMillis() + MAX_WAIT;
|
||||
|
||||
return Math.min(targetTime, Math.min(furthestTime, job.getRetryUntil()));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user