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.pull/1/head
parent
8d6b9ae43e
commit
36ec1d84a1
@ -1,9 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class AuthorizationFailedException extends IOException {
|
||||
public AuthorizationFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -1,6 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ExpectationFailedException extends IOException {
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NotFoundException extends IOException {
|
||||
public NotFoundException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class RateLimitException extends IOException {
|
||||
public RateLimitException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class StaleDevicesException extends IOException {
|
||||
|
||||
private final StaleDevices staleDevices;
|
||||
|
||||
public StaleDevicesException(StaleDevices staleDevices) {
|
||||
this.staleDevices = staleDevices;
|
||||
}
|
||||
|
||||
public StaleDevices getStaleDevices() {
|
||||
return staleDevices;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
public class AuthorizationFailedException extends NonSuccessfulResponseCodeException {
|
||||
public AuthorizationFailedException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
public class ExpectationFailedException extends NonSuccessfulResponseCodeException {
|
||||
}
|
||||
@ -1,8 +1,8 @@
|
||||
package org.whispersystems.textsecure.push;
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.whispersystems.textsecure.push.MismatchedDevices;
|
||||
|
||||
public class MismatchedDevicesException extends IOException {
|
||||
public class MismatchedDevicesException extends NonSuccessfulResponseCodeException {
|
||||
|
||||
private final MismatchedDevices mismatchedDevices;
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class NonSuccessfulResponseCodeException extends IOException {
|
||||
|
||||
public NonSuccessfulResponseCodeException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NonSuccessfulResponseCodeException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
public class NotFoundException extends NonSuccessfulResponseCodeException {
|
||||
public NotFoundException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class PushNetworkException extends IOException {
|
||||
public PushNetworkException(Exception exception) {
|
||||
super(exception);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
public class RateLimitException extends NonSuccessfulResponseCodeException {
|
||||
public RateLimitException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package org.whispersystems.textsecure.push.exceptions;
|
||||
|
||||
import org.whispersystems.textsecure.push.StaleDevices;
|
||||
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||
|
||||
public class StaleDevicesException extends NonSuccessfulResponseCodeException {
|
||||
|
||||
private final StaleDevices staleDevices;
|
||||
|
||||
public StaleDevicesException(StaleDevices staleDevices) {
|
||||
this.staleDevices = staleDevices;
|
||||
}
|
||||
|
||||
public StaleDevices getStaleDevices() {
|
||||
return staleDevices;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.path.android.jobqueue.JobManager;
|
||||
import com.path.android.jobqueue.config.Configuration;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
||||
import org.thoughtcrime.securesms.jobs.ContextInjector;
|
||||
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.JobLogger;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
/**
|
||||
* Will be called once when the TextSecure process is created.
|
||||
*
|
||||
* We're using this as an insertion point to patch up the Android PRNG disaster,
|
||||
* to initialize the job manager, and to check for GCM registration freshness.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ApplicationContext extends Application {
|
||||
|
||||
private JobManager jobManager;
|
||||
|
||||
public static ApplicationContext getInstance(Context context) {
|
||||
return (ApplicationContext)context.getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
initializeRandomNumberFix();
|
||||
initializeJobManager();
|
||||
initializeGcmCheck();
|
||||
}
|
||||
|
||||
public JobManager getJobManager() {
|
||||
return jobManager;
|
||||
}
|
||||
|
||||
private void initializeRandomNumberFix() {
|
||||
PRNGFixes.apply();
|
||||
}
|
||||
|
||||
private void initializeJobManager() {
|
||||
Configuration configuration = new Configuration.Builder(this)
|
||||
.minConsumerCount(1)
|
||||
.injector(new ContextInjector(this))
|
||||
.customLogger(new JobLogger())
|
||||
.build();
|
||||
|
||||
this.jobManager = new JobManager(this, configuration);
|
||||
}
|
||||
|
||||
private void initializeGcmCheck() {
|
||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
||||
TextSecurePreferences.getGcmRegistrationId(this) == null)
|
||||
{
|
||||
this.jobManager.addJob(new GcmRefreshJob());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
||||
|
||||
/**
|
||||
* Will be called once when the TextSecure process is created.
|
||||
*
|
||||
* We're using this as an insertion point to patch up the Android PRNG disaster.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class ApplicationListener extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
PRNGFixes.apply();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
public class Priorities {
|
||||
|
||||
public static final int NORMAL = 500;
|
||||
public static final int HIGH = 1000;
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue