Implement delivery receipts.

1) Support a "receipt" push message type.

2) Identify messages by timestamp.

3) Introduce a JobManager to handle the queue for network
   dependent jobs.
This commit is contained in:
Moxie Marlinspike
2014-07-25 15:14:29 -07:00
parent 8d6b9ae43e
commit 36ec1d84a1
48 changed files with 739 additions and 271 deletions

View File

@@ -0,0 +1,22 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import com.path.android.jobqueue.BaseJob;
import com.path.android.jobqueue.di.DependencyInjector;
public class ContextInjector implements DependencyInjector {
private final Context context;
public ContextInjector(Context context) {
this.context = context;
}
@Override
public void inject(BaseJob job) {
if (job instanceof ContextJob) {
((ContextJob)job).setContext(context);
}
}
}

View File

@@ -0,0 +1,23 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import com.path.android.jobqueue.Job;
import com.path.android.jobqueue.Params;
public abstract class ContextJob extends Job {
transient protected Context context;
protected ContextJob(Params params) {
super(params);
}
public void setContext(Context context) {
this.context = context;
}
protected Context getContext() {
return context;
}
}

View File

@@ -0,0 +1,56 @@
package org.thoughtcrime.securesms.jobs;
import android.util.Log;
import com.path.android.jobqueue.Params;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
public class DeliveryReceiptJob extends ContextJob {
private static final String TAG = DeliveryReceiptJob.class.getSimpleName();
private final String destination;
private final long timestamp;
private final String relay;
public DeliveryReceiptJob(String destination, long timestamp, String relay) {
super(new Params(Priorities.HIGH).requireNetwork().persist());
this.destination = destination;
this.timestamp = timestamp;
this.relay = relay;
}
@Override
public void onAdded() {}
@Override
public void onRun() throws Throwable {
Log.w("DeliveryReceiptJob", "Sending delivery receipt...");
PushServiceSocket socket = PushServiceSocketFactory.create(context);
socket.sendReceipt(destination, timestamp, relay);
}
@Override
protected void onCancel() {
Log.w(TAG, "Failed to send receipt after retry exhausted!");
}
@Override
protected boolean shouldReRunOnThrowable(Throwable throwable) {
Log.w(TAG, throwable);
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
if (throwable instanceof PushNetworkException) return true;
return false;
}
@Override
protected int getRetryLimit() {
return 50;
}
}

View File

@@ -0,0 +1,61 @@
package org.thoughtcrime.securesms.jobs;
import android.util.Log;
import android.widget.Toast;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;
import com.path.android.jobqueue.Params;
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
public class GcmRefreshJob extends ContextJob {
private static final String TAG = GcmRefreshJob.class.getSimpleName();
public static final String REGISTRATION_ID = "312334754206";
public GcmRefreshJob() {
super(new Params(Priorities.NORMAL).requireNetwork());
}
@Override
public void onAdded() {}
@Override
public void onRun() throws Exception {
String registrationId = TextSecurePreferences.getGcmRegistrationId(context);
if (registrationId == null) {
Log.w(TAG, "GCM registrationId expired, reregistering...");
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
if (result != ConnectionResult.SUCCESS) {
Toast.makeText(context, "Unable to register with GCM!", Toast.LENGTH_LONG).show();
}
String gcmId = GoogleCloudMessaging.getInstance(context).register(REGISTRATION_ID);
PushServiceSocket socket = PushServiceSocketFactory.create(context);
socket.registerGcmId(gcmId);
TextSecurePreferences.setGcmRegistrationId(context, gcmId);
}
}
@Override
protected void onCancel() {
Log.w(TAG, "GCM reregistration failed after retry attempt exhaustion!");
}
@Override
protected boolean shouldReRunOnThrowable(Throwable throwable) {
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
return true;
}
}

View File

@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.jobs;
import android.util.Log;
import com.path.android.jobqueue.log.CustomLogger;
public class JobLogger implements CustomLogger {
@Override
public boolean isDebugEnabled() {
return false;
}
@Override
public void d(String text, Object... args) {
Log.w("JobManager", String.format(text, args));
}
@Override
public void e(Throwable t, String text, Object... args) {
Log.w("JobManager", String.format(text, args), t);
}
@Override
public void e(String text, Object... args) {
Log.w("JobManager", String.format(text, args));
}
}

View File

@@ -0,0 +1,8 @@
package org.thoughtcrime.securesms.jobs;
public class Priorities {
public static final int NORMAL = 500;
public static final int HIGH = 1000;
}