Persistent job queue, derivative of android-priority-jobqueue.

// FREEBIE
This commit is contained in:
Moxie Marlinspike 2014-08-03 17:14:54 -07:00
parent 20cf775b1e
commit 544f06451f
29 changed files with 1017 additions and 1 deletions

1
jobqueue/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

19
jobqueue/build.gradle Normal file
View File

@ -0,0 +1,19 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 20
buildToolsVersion "20.0.0"
defaultConfig {
applicationId "org.whispersystems.jobqueue"
minSdkVersion 9
targetSdkVersion 19
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
}

View File

@ -0,0 +1,90 @@
package org.whispersystems.jobqueue;
import android.test.AndroidTestCase;
import org.whispersystems.jobqueue.jobs.PersistentTestJob;
import org.whispersystems.jobqueue.jobs.RequirementTestJob;
import org.whispersystems.jobqueue.jobs.TestJob;
import org.whispersystems.jobqueue.persistence.JavaJobSerializer;
import org.whispersystems.jobqueue.util.MockRequirement;
import org.whispersystems.jobqueue.util.MockRequirementProvider;
import org.whispersystems.jobqueue.util.PersistentMockRequirement;
import org.whispersystems.jobqueue.util.PersistentRequirement;
import org.whispersystems.jobqueue.util.PersistentResult;
public class JobManagerTest extends AndroidTestCase {
public void testTransientJobExecution() throws InterruptedException {
TestJob testJob = new TestJob();
JobManager jobManager = new JobManager(getContext(), "transient-test", null, null, 1);
jobManager.add(testJob);
assertTrue(testJob.isAdded());
assertTrue(testJob.isRan());
}
public void testTransientRequirementJobExecution() throws InterruptedException {
MockRequirementProvider provider = new MockRequirementProvider();
MockRequirement requirement = new MockRequirement(false);
TestJob testJob = new RequirementTestJob(requirement);
JobManager jobManager = new JobManager(getContext(), "transient-requirement-test", provider, null, 1);
jobManager.add(testJob);
assertTrue(testJob.isAdded());
assertTrue(!testJob.isRan());
requirement.setPresent(true);
provider.fireChange();
assertTrue(testJob.isRan());
}
public void testPersistentJobExecuton() throws InterruptedException {
PersistentMockRequirement requirement = new PersistentMockRequirement();
PersistentTestJob testJob = new PersistentTestJob(requirement);
JobManager jobManager = new JobManager(getContext(), "persistent-requirement-test3", null, new JavaJobSerializer(getContext()), 1);
PersistentResult.getInstance().reset();
PersistentRequirement.getInstance().setPresent(false);
jobManager.add(testJob);
assertTrue(PersistentResult.getInstance().isAdded());
assertTrue(!PersistentResult.getInstance().isRan());
PersistentRequirement.getInstance().setPresent(true);
jobManager = new JobManager(getContext(), "persistent-requirement-test3", null, new JavaJobSerializer(getContext()), 1);
assertTrue(PersistentResult.getInstance().isRan());
}
public void testEncryptedJobExecuton() throws InterruptedException {
EncryptionKeys keys = new EncryptionKeys("foobar");
PersistentMockRequirement requirement = new PersistentMockRequirement();
PersistentTestJob testJob = new PersistentTestJob(requirement, keys);
JobManager jobManager = new JobManager(getContext(), "persistent-requirement-test4", null, new JavaJobSerializer(getContext()), 1);
jobManager.setEncryptionKeys(keys);
PersistentResult.getInstance().reset();
PersistentRequirement.getInstance().setPresent(false);
jobManager.add(testJob);
assertTrue(PersistentResult.getInstance().isAdded());
assertTrue(!PersistentResult.getInstance().isRan());
PersistentRequirement.getInstance().setPresent(true);
jobManager = new JobManager(getContext(), "persistent-requirement-test4", null, new JavaJobSerializer(getContext()), 1);
assertTrue(!PersistentResult.getInstance().isRan());
jobManager.setEncryptionKeys(keys);
assertTrue(PersistentResult.getInstance().isRan());
}
}

View File

@ -0,0 +1,39 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.Requirement;
import org.whispersystems.jobqueue.util.PersistentResult;
public class PersistentTestJob extends Job {
public PersistentTestJob(Requirement requirement) {
super(JobParameters.newBuilder().withRequirement(requirement).withPersistence().create());
}
public PersistentTestJob(Requirement requirement, EncryptionKeys keys) {
super(JobParameters.newBuilder().withRequirement(requirement).withPersistence().withEncryption(keys).create());
}
@Override
public void onAdded() {
PersistentResult.getInstance().onAdded();;
}
@Override
public void onRun() throws Throwable {
PersistentResult.getInstance().onRun();
}
@Override
public void onCanceled() {
PersistentResult.getInstance().onCanceled();
}
@Override
public boolean onShouldRetry(Throwable throwable) {
return false;
}
}

View File

@ -0,0 +1,12 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.Requirement;
public class RequirementTestJob extends TestJob {
public RequirementTestJob(Requirement requirement) {
super(JobParameters.newBuilder().withRequirement(requirement).create());
}
}

View File

@ -0,0 +1,71 @@
package org.whispersystems.jobqueue.jobs;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.JobParameters;
public class TestJob extends Job {
private final Object ADDED_LOCK = new Object();
private final Object RAN_LOCK = new Object();
private final Object CANCELED_LOCK = new Object();
private boolean added = false;
private boolean ran = false;
private boolean canceled = false;
public TestJob() {
this(JobParameters.newBuilder().create());
}
public TestJob(JobParameters parameters) {
super(parameters);
}
@Override
public void onAdded() {
synchronized (ADDED_LOCK) {
this.added = true;
this.ADDED_LOCK.notifyAll();
}
}
@Override
public void onRun() throws Throwable {
synchronized (RAN_LOCK) {
this.ran = true;
}
}
@Override
public void onCanceled() {
synchronized (CANCELED_LOCK) {
this.canceled = true;
}
}
@Override
public boolean onShouldRetry(Throwable throwable) {
return false;
}
public boolean isAdded() throws InterruptedException {
synchronized (ADDED_LOCK) {
if (!added) ADDED_LOCK.wait(1000);
return added;
}
}
public boolean isRan() throws InterruptedException {
synchronized (RAN_LOCK) {
if (!ran) RAN_LOCK.wait(1000);
return ran;
}
}
public boolean isCanceled() throws InterruptedException {
synchronized (CANCELED_LOCK) {
if (!canceled) CANCELED_LOCK.wait(1000);
return canceled;
}
}
}

View File

@ -0,0 +1,23 @@
package org.whispersystems.jobqueue.util;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.util.concurrent.atomic.AtomicBoolean;
public class MockRequirement implements Requirement {
private AtomicBoolean present;
public MockRequirement(boolean present) {
this.present = new AtomicBoolean(present);
}
public void setPresent(boolean present) {
this.present.set(present);
}
@Override
public boolean isPresent() {
return present.get();
}
}

View File

@ -0,0 +1,18 @@
package org.whispersystems.jobqueue.util;
import org.whispersystems.jobqueue.requirements.RequirementListener;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
public class MockRequirementProvider implements RequirementProvider {
private RequirementListener listener;
public void fireChange() {
listener.onRequirementStatusChanged();
}
@Override
public void setListener(RequirementListener listener) {
this.listener = listener;
}
}

View File

@ -0,0 +1,10 @@
package org.whispersystems.jobqueue.util;
import org.whispersystems.jobqueue.requirements.Requirement;
public class PersistentMockRequirement implements Requirement {
@Override
public boolean isPresent() {
return PersistentRequirement.getInstance().isPresent();
}
}

View File

@ -0,0 +1,22 @@
package org.whispersystems.jobqueue.util;
import java.util.concurrent.atomic.AtomicBoolean;
public class PersistentRequirement {
private AtomicBoolean present = new AtomicBoolean(false);
private static final PersistentRequirement instance = new PersistentRequirement();
public static PersistentRequirement getInstance() {
return instance;
}
public void setPresent(boolean present) {
this.present.set(present);
}
public boolean isPresent() {
return present.get();
}
}

View File

@ -0,0 +1,73 @@
package org.whispersystems.jobqueue.util;
public class PersistentResult {
private final Object ADDED_LOCK = new Object();
private final Object RAN_LOCK = new Object();
private final Object CANCELED_LOCK = new Object();
private boolean added = false;
private boolean ran = false;
private boolean canceled = false;
private static final PersistentResult instance = new PersistentResult();
public static PersistentResult getInstance() {
return instance;
}
public void onAdded() {
synchronized (ADDED_LOCK) {
this.added = true;
this.ADDED_LOCK.notifyAll();
}
}
public void onRun() throws Throwable {
synchronized (RAN_LOCK) {
this.ran = true;
}
}
public void onCanceled() {
synchronized (CANCELED_LOCK) {
this.canceled = true;
}
}
public boolean isAdded() throws InterruptedException {
synchronized (ADDED_LOCK) {
if (!added) ADDED_LOCK.wait(1000);
return added;
}
}
public boolean isRan() throws InterruptedException {
synchronized (RAN_LOCK) {
if (!ran) RAN_LOCK.wait(1000);
return ran;
}
}
public boolean isCanceled() throws InterruptedException {
synchronized (CANCELED_LOCK) {
if (!canceled) CANCELED_LOCK.wait(1000);
return canceled;
}
}
public void reset() {
synchronized (ADDED_LOCK) {
this.added = false;
}
synchronized (RAN_LOCK) {
this.ran = false;
}
synchronized (CANCELED_LOCK) {
this.canceled = false;
}
}
}

View File

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.whispersystems.jobqueue">
<application />
</manifest>

View File

@ -0,0 +1,14 @@
package org.whispersystems.jobqueue;
public class EncryptionKeys {
private transient final String keys;
public EncryptionKeys(String keys) {
this.keys = keys;
}
public String getKeys() {
return keys;
}
}

View File

@ -0,0 +1,60 @@
package org.whispersystems.jobqueue;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.io.Serializable;
import java.util.List;
public abstract class Job implements Serializable {
private final JobParameters parameters;
private transient long persistentId;
public Job(JobParameters parameters) {
this.parameters = parameters;
}
public List<Requirement> getRequirements() {
return parameters.getRequirements();
}
public boolean isRequirementsMet() {
for (Requirement requirement : parameters.getRequirements()) {
if (!requirement.isPresent()) return false;
}
return true;
}
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 void setPersistentId(long persistentId) {
this.persistentId = persistentId;
}
public long getPersistentId() {
return persistentId;
}
public abstract void onAdded();
public abstract void onRun() throws Throwable;
public abstract void onCanceled();
public abstract boolean onShouldRetry(Throwable throwable);
}

View File

@ -0,0 +1,48 @@
package org.whispersystems.jobqueue;
import org.whispersystems.jobqueue.persistence.PersistentStorage;
public class JobConsumer extends Thread {
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();
if (!runJob(job)) {
job.onCanceled();
}
if (job.isPersistent()) {
persistentStorage.remove(job.getPersistentId());
}
}
}
private boolean runJob(Job job) {
int retryCount = job.getRetryCount();
for (int i=retryCount;i>0;i--) {
try {
job.onRun();
return true;
} catch (Throwable throwable) {
if (!job.onShouldRetry(throwable)) {
return false;
}
}
}
return false;
}
}

View File

@ -0,0 +1,95 @@
package org.whispersystems.jobqueue;
import android.content.Context;
import android.util.Log;
import org.whispersystems.jobqueue.persistence.JobSerializer;
import org.whispersystems.jobqueue.persistence.PersistentStorage;
import org.whispersystems.jobqueue.requirements.RequirementListener;
import org.whispersystems.jobqueue.requirements.RequirementProvider;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
public class JobManager implements RequirementListener {
private final JobQueue jobQueue = new JobQueue();
private final Executor eventExecutor = Executors.newSingleThreadExecutor();
private final AtomicBoolean hasLoadedEncrypted = new AtomicBoolean(false);
private final PersistentStorage persistentStorage;
public JobManager(Context context, String name,
RequirementProvider requirementProvider,
JobSerializer jobSerializer, int consumers)
{
this.persistentStorage = new PersistentStorage(context, name, jobSerializer);
eventExecutor.execute(new LoadTask(null));
if (requirementProvider != null) {
requirementProvider.setListener(this);
}
for (int i=0;i<consumers;i++) {
new JobConsumer("JobConsumer-" + i, jobQueue, persistentStorage).start();
}
}
public void setEncryptionKeys(EncryptionKeys keys) {
if (hasLoadedEncrypted.compareAndSet(false, true)) {
eventExecutor.execute(new LoadTask(keys));
}
}
public void add(final Job job) {
eventExecutor.execute(new Runnable() {
@Override
public void run() {
try {
if (job.isPersistent()) {
persistentStorage.store(job);
}
job.onAdded();
jobQueue.add(job);
} catch (IOException e) {
Log.w("JobManager", e);
job.onCanceled();
}
}
});
}
@Override
public void onRequirementStatusChanged() {
eventExecutor.execute(new Runnable() {
@Override
public void run() {
jobQueue.onRequirementStatusChanged();
}
});
}
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);
}
}
}

View File

@ -0,0 +1,82 @@
package org.whispersystems.jobqueue;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
public class JobParameters implements Serializable {
private transient EncryptionKeys encryptionKeys;
private final List<Requirement> requirements;
private final boolean isPersistent;
private final int retryCount;
private JobParameters(List<Requirement> requirements,
boolean isPersistent,
EncryptionKeys encryptionKeys,
int retryCount)
{
this.requirements = requirements;
this.isPersistent = isPersistent;
this.encryptionKeys = encryptionKeys;
this.retryCount = retryCount;
}
public List<Requirement> getRequirements() {
return requirements;
}
public boolean isPersistent() {
return isPersistent;
}
public EncryptionKeys getEncryptionKeys() {
return encryptionKeys;
}
public void setEncryptionKeys(EncryptionKeys encryptionKeys) {
this.encryptionKeys = encryptionKeys;
}
public int getRetryCount() {
return retryCount;
}
public static Builder newBuilder() {
return new Builder();
}
public static class Builder {
private List<Requirement> requirements = new LinkedList<>();
private boolean isPersistent = false;
private EncryptionKeys encryptionKeys = null;
private int retryCount = 100;
public Builder withRequirement(Requirement requirement) {
this.requirements.add(requirement);
return this;
}
public Builder withPersistence() {
this.isPersistent = true;
return this;
}
public Builder withEncryption(EncryptionKeys encryptionKeys) {
this.encryptionKeys = encryptionKeys;
return this;
}
public Builder withRetryCount(int retryCount) {
this.retryCount = retryCount;
return this;
}
public JobParameters create() {
return new JobParameters(requirements, isPersistent, encryptionKeys, retryCount);
}
}
}

View File

@ -0,0 +1,54 @@
package org.whispersystems.jobqueue;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class JobQueue {
private final LinkedList<Job> jobQueue = new LinkedList<>();
public synchronized void onRequirementStatusChanged() {
notifyAll();
}
public synchronized void add(Job job) {
jobQueue.add(job);
notifyAll();
}
public synchronized void addAll(List<Job> jobs) {
jobQueue.addAll(jobs);
notifyAll();
}
public synchronized Job getNext() {
try {
Job nextAvailableJob;
while ((nextAvailableJob = getNextAvailableJob()) == null) {
wait();
}
return nextAvailableJob;
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
private Job getNextAvailableJob() {
if (jobQueue.isEmpty()) return null;
ListIterator<Job> iterator = jobQueue.listIterator();
while (iterator.hasNext()) {
Job job = iterator.next();
if (job.isRequirementsMet()) {
iterator.remove();
return job;
}
}
return null;
}
}

View File

@ -0,0 +1,7 @@
package org.whispersystems.jobqueue.dependencies;
import android.content.Context;
public interface ContextDependent {
public void setContext(Context context);
}

View File

@ -0,0 +1,59 @@
package org.whispersystems.jobqueue.persistence;
import android.content.Context;
import android.util.Base64;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import org.whispersystems.jobqueue.dependencies.ContextDependent;
import org.whispersystems.jobqueue.requirements.Requirement;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class JavaJobSerializer implements JobSerializer {
private final Context context;
public JavaJobSerializer(Context context) {
this.context = context;
}
@Override
public String serialize(Job job) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(job);
return Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
}
@Override
public Job deserialize(EncryptionKeys keys, boolean encrypted, String serialized) throws IOException {
try {
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decode(serialized, Base64.NO_WRAP));
ObjectInputStream ois = new ObjectInputStream(bais);
Job job = (Job)ois.readObject();
if (job instanceof ContextDependent) {
((ContextDependent)job).setContext(context);
}
for (Requirement requirement : job.getRequirements()) {
if (requirement instanceof ContextDependent) {
((ContextDependent)requirement).setContext(context);
}
}
job.setEncryptionKeys(keys);
return job;
} catch (ClassNotFoundException e) {
throw new IOException(e);
}
}
}

View File

@ -0,0 +1,13 @@
package org.whispersystems.jobqueue.persistence;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import java.io.IOException;
public interface JobSerializer {
public String serialize(Job job) throws IOException;
public Job deserialize(EncryptionKeys keys, boolean encrypted, String serialized) throws IOException;
}

View File

@ -0,0 +1,112 @@
package org.whispersystems.jobqueue.persistence;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import org.whispersystems.jobqueue.EncryptionKeys;
import org.whispersystems.jobqueue.Job;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class PersistentStorage {
private static final int DATABASE_VERSION = 1;
private static final String TABLE_NAME = "queue";
private static final String ID = "_id";
private static final String ITEM = "item";
private static final String ENCRYPTED = "encrypted";
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 DatabaseHelper databaseHelper;
private final JobSerializer jobSerializer;
public PersistentStorage(Context context, String name, JobSerializer serializer) {
this.databaseHelper = new DatabaseHelper(context, "_jobqueue-" + name);
this.jobSerializer = serializer;
}
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> getAll(EncryptionKeys keys) {
// return getJobs(keys, null);
// }
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();
Cursor cursor = null;
try {
cursor = database.query(TABLE_NAME, null, where, null, null, null, ID + " ASC", null);
while (cursor.moveToNext()) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
String item = cursor.getString(cursor.getColumnIndexOrThrow(ITEM));
boolean encrypted = cursor.getInt(cursor.getColumnIndexOrThrow(ENCRYPTED)) == 1;
try{
Job job = jobSerializer.deserialize(keys, encrypted, item);
job.setPersistentId(id);
results.add(job);
} catch (IOException e) {
Log.w("PersistentStore", e);
remove(id);
}
}
} finally {
if (cursor != null)
cursor.close();
}
return results;
}
public void remove(long id) {
databaseHelper.getWritableDatabase()
.delete(TABLE_NAME, ID + " = ?", new String[] {String.valueOf(id)});
}
private static class DatabaseHelper extends SQLiteOpenHelper {
public DatabaseHelper(Context context, String name) {
super(context, name, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}

View File

@ -0,0 +1,31 @@
package org.whispersystems.jobqueue.requirements;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import org.whispersystems.jobqueue.dependencies.ContextDependent;
public class NetworkRequirement implements Requirement, ContextDependent {
private transient Context context;
public NetworkRequirement(Context context) {
this.context = context;
}
public NetworkRequirement() {}
@Override
public boolean isPresent() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
return netInfo != null && netInfo.isConnectedOrConnecting();
}
@Override
public void setContext(Context context) {
this.context = context;
}
}

View File

@ -0,0 +1,38 @@
package org.whispersystems.jobqueue.requirements;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
public class NetworkRequirementProvider implements RequirementProvider {
private RequirementListener listener;
public NetworkRequirementProvider(Context context) {
context.getApplicationContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (listener == null) {
return;
}
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
boolean connected = netInfo != null && netInfo.isConnectedOrConnecting();
if (connected) {
listener.onRequirementStatusChanged();
}
}
}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
@Override
public void setListener(RequirementListener listener) {
this.listener = listener;
}
}

View File

@ -0,0 +1,7 @@
package org.whispersystems.jobqueue.requirements;
import java.io.Serializable;
public interface Requirement extends Serializable {
public boolean isPresent();
}

View File

@ -0,0 +1,5 @@
package org.whispersystems.jobqueue.requirements;
public interface RequirementListener {
public void onRequirementStatusChanged();
}

View File

@ -0,0 +1,5 @@
package org.whispersystems.jobqueue.requirements;
public interface RequirementProvider {
public void setListener(RequirementListener listener);
}

View File

@ -0,0 +1,2 @@
<resources>
</resources>

View File

@ -1 +1 @@
include ':library', ':libaxolotl' include ':library', ':libaxolotl', ':jobqueue'