diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7b785773b8..6581bffee7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -37,7 +37,7 @@
android:protectionLevel="signature" />
-
diff --git a/build.gradle b/build.gradle
index 0be9e214a2..483d14b627 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,6 +36,7 @@ dependencies {
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'org.w3c:smil:1.0.0'
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
+ compile 'com.path:android-priority-jobqueue:1.1.2'
androidTestCompile 'com.squareup:fest-android:1.0.8'
@@ -56,6 +57,8 @@ dependencyVerification {
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
+ 'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
+ 'com.path:android-priority-jobqueue:af8d0dc930c3640518e9548ec887cf7871ab5e3c1ea634a4553dd5c30dd6e4a8'
]
}
diff --git a/library/build.gradle b/library/build.gradle
index 70792c51c5..20963549a5 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -19,7 +19,7 @@ repositories {
}
dependencies {
- compile 'com.google.protobuf:protobuf-java:2.4.1'
+ compile 'com.google.protobuf:protobuf-java:2.5.0'
compile 'com.madgag:sc-light-jdk15on:1.47.0.2'
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
compile 'org.whispersystems:gson:2.2.4'
diff --git a/library/protobuf/IncomingPushMessageSignal.proto b/library/protobuf/IncomingPushMessageSignal.proto
index bbd91167df..88a4c5ed4d 100644
--- a/library/protobuf/IncomingPushMessageSignal.proto
+++ b/library/protobuf/IncomingPushMessageSignal.proto
@@ -10,6 +10,7 @@ message IncomingPushMessageSignal {
KEY_EXCHANGE = 2;
PREKEY_BUNDLE = 3;
PLAINTEXT = 4;
+ RECEIPT = 5;
}
optional Type type = 1;
optional string source = 2;
diff --git a/library/src/org/whispersystems/textsecure/push/AuthorizationFailedException.java b/library/src/org/whispersystems/textsecure/push/AuthorizationFailedException.java
deleted file mode 100644
index af3ca251fa..0000000000
--- a/library/src/org/whispersystems/textsecure/push/AuthorizationFailedException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.whispersystems.textsecure.push;
-
-import java.io.IOException;
-
-public class AuthorizationFailedException extends IOException {
- public AuthorizationFailedException(String s) {
- super(s);
- }
-}
diff --git a/library/src/org/whispersystems/textsecure/push/ExpectationFailedException.java b/library/src/org/whispersystems/textsecure/push/ExpectationFailedException.java
deleted file mode 100644
index defc66d20d..0000000000
--- a/library/src/org/whispersystems/textsecure/push/ExpectationFailedException.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package org.whispersystems.textsecure.push;
-
-import java.io.IOException;
-
-public class ExpectationFailedException extends IOException {
-}
diff --git a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
index d4f753469a..e04b96587d 100644
--- a/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
+++ b/library/src/org/whispersystems/textsecure/push/IncomingPushMessage.java
@@ -138,4 +138,12 @@ public class IncomingPushMessage implements Parcelable {
public boolean isPreKeyBundle() {
return getType() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
}
+
+ public boolean isReceipt() {
+ return getType() == IncomingPushMessageSignal.Type.RECEIPT_VALUE;
+ }
+
+ public boolean isPlaintext() {
+ return getType() == IncomingPushMessageSignal.Type.PLAINTEXT_VALUE;
+ }
}
diff --git a/library/src/org/whispersystems/textsecure/push/NotFoundException.java b/library/src/org/whispersystems/textsecure/push/NotFoundException.java
deleted file mode 100644
index b27d791e39..0000000000
--- a/library/src/org/whispersystems/textsecure/push/NotFoundException.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.whispersystems.textsecure.push;
-
-import java.io.IOException;
-
-public class NotFoundException extends IOException {
- public NotFoundException(String s) {
- super(s);
- }
-}
diff --git a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java
index 3b8525857b..e6dbd65527 100644
--- a/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java
+++ b/library/src/org/whispersystems/textsecure/push/OutgoingPushMessageList.java
@@ -1,6 +1,5 @@
package org.whispersystems.textsecure.push;
-import java.util.LinkedList;
import java.util.List;
public class OutgoingPushMessageList {
@@ -9,9 +8,14 @@ public class OutgoingPushMessageList {
private String relay;
+ private long timestamp;
+
private List messages;
- public OutgoingPushMessageList(String destination, String relay, List messages) {
+ public OutgoingPushMessageList(String destination, long timestamp, String relay,
+ List messages)
+ {
+ this.timestamp = timestamp;
this.destination = destination;
this.relay = relay;
this.messages = messages;
@@ -28,4 +32,8 @@ public class OutgoingPushMessageList {
public String getRelay() {
return relay;
}
+
+ public long getTimestamp() {
+ return timestamp;
+ }
}
diff --git a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java
index 6f953827bf..d5e8939714 100644
--- a/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java
+++ b/library/src/org/whispersystems/textsecure/push/PushMessageProtos.java
@@ -240,6 +240,10 @@ public final class PushMessageProtos {
* PLAINTEXT = 4;
*/
PLAINTEXT(4, 4),
+ /**
+ * RECEIPT = 5;
+ */
+ RECEIPT(5, 5),
;
/**
@@ -262,6 +266,10 @@ public final class PushMessageProtos {
* PLAINTEXT = 4;
*/
public static final int PLAINTEXT_VALUE = 4;
+ /**
+ * RECEIPT = 5;
+ */
+ public static final int RECEIPT_VALUE = 5;
public final int getNumber() { return value; }
@@ -273,6 +281,7 @@ public final class PushMessageProtos {
case 2: return KEY_EXCHANGE;
case 3: return PREKEY_BUNDLE;
case 4: return PLAINTEXT;
+ case 5: return RECEIPT;
default: return null;
}
}
@@ -4079,28 +4088,28 @@ public final class PushMessageProtos {
static {
java.lang.String[] descriptorData = {
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
- "ecure\"\207\002\n\031IncomingPushMessageSignal\0228\n\004t" +
+ "ecure\"\224\002\n\031IncomingPushMessageSignal\0228\n\004t" +
"ype\030\001 \001(\0162*.textsecure.IncomingPushMessa" +
"geSignal.Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceD" +
"evice\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030" +
- "\005 \001(\004\022\017\n\007message\030\006 \001(\014\"W\n\004Type\022\013\n\007UNKNOW" +
+ "\005 \001(\004\022\017\n\007message\030\006 \001(\014\"d\n\004Type\022\013\n\007UNKNOW" +
"N\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n" +
- "\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\"\207\004\n\022Push" +
- "MessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013attachme" +
- "nts\030\002 \003(\01320.textsecure.PushMessageConten",
- "t.AttachmentPointer\022:\n\005group\030\003 \001(\0132+.tex" +
- "tsecure.PushMessageContent.GroupContext\022" +
- "\r\n\005flags\030\004 \001(\r\032A\n\021AttachmentPointer\022\n\n\002i" +
- "d\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(" +
- "\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" +
- " \001(\01620.textsecure.PushMessageContent.Gro" +
- "upContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030" +
- "\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushM" +
- "essageContent.AttachmentPointer\"6\n\004Type\022" +
- "\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n",
- "\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SESSION\020\001B7\n\"org" +
- ".whispersystems.textsecure.pushB\021PushMes" +
- "sageProtos"
+ "\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\022\013\n\007RECEI" +
+ "PT\020\005\"\207\004\n\022PushMessageContent\022\014\n\004body\030\001 \001(" +
+ "\t\022E\n\013attachments\030\002 \003(\01320.textsecure.Push",
+ "MessageContent.AttachmentPointer\022:\n\005grou" +
+ "p\030\003 \001(\0132+.textsecure.PushMessageContent." +
+ "GroupContext\022\r\n\005flags\030\004 \001(\r\032A\n\021Attachmen" +
+ "tPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002 \001(" +
+ "\t\022\013\n\003key\030\003 \001(\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 " +
+ "\001(\014\022>\n\004type\030\002 \001(\01620.textsecure.PushMessa" +
+ "geContent.GroupContext.Type\022\014\n\004name\030\003 \001(" +
+ "\t\022\017\n\007members\030\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.tex" +
+ "tsecure.PushMessageContent.AttachmentPoi" +
+ "nter\"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n",
+ "\007DELIVER\020\002\022\010\n\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SES" +
+ "SION\020\001B7\n\"org.whispersystems.textsecure." +
+ "pushB\021PushMessageProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
diff --git a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
index 3ff808c1d0..c2d0a11bcc 100644
--- a/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
+++ b/library/src/org/whispersystems/textsecure/push/PushServiceSocket.java
@@ -25,9 +25,17 @@ import com.google.thoughtcrimegson.JsonParseException;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
-import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.state.PreKeyBundle;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
+import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
+import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException;
+import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
+import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
+import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
+import org.whispersystems.textsecure.push.exceptions.NotFoundException;
+import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
+import org.whispersystems.textsecure.push.exceptions.RateLimitException;
+import org.whispersystems.textsecure.push.exceptions.StaleDevicesException;
import org.whispersystems.textsecure.util.Base64;
import org.whispersystems.textsecure.util.BlacklistingTrustManager;
import org.whispersystems.textsecure.util.Util;
@@ -75,6 +83,7 @@ public class PushServiceSocket {
private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens";
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
private static final String MESSAGE_PATH = "/v1/messages/%s";
+ private static final String RECEIPT_PATH = "/v1/receipt/%s/%d";
private static final String ATTACHMENT_PATH = "/v1/attachments/%s";
private static final boolean ENFORCE_SSL = true;
@@ -109,6 +118,16 @@ public class PushServiceSocket {
"PUT", new Gson().toJson(signalingKeyEntity));
}
+ public void sendReceipt(String destination, long messageId, String relay) throws IOException {
+ String path = String.format(RECEIPT_PATH, destination, messageId);
+
+ if (!Util.isEmpty(relay)) {
+ path += "?relay=" + relay;
+ }
+
+ makeRequest(path, "PUT", null);
+ }
+
public void registerGcmId(String gcmRegistrationId) throws IOException {
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
@@ -380,68 +399,77 @@ public class PushServiceSocket {
}
private String makeRequest(String urlFragment, String method, String body)
- throws IOException
+ throws NonSuccessfulResponseCodeException, PushNetworkException
{
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
- String response = Util.readFully(connection.getInputStream());
- connection.disconnect();
+ try {
+ String response = Util.readFully(connection.getInputStream());
+ connection.disconnect();
- return response;
+ return response;
+ } catch (IOException ioe) {
+ throw new PushNetworkException(ioe);
+ }
}
private HttpURLConnection makeBaseRequest(String urlFragment, String method, String body)
- throws IOException
+ throws NonSuccessfulResponseCodeException, PushNetworkException
{
- HttpURLConnection connection = getConnection(urlFragment, method);
+ try {
+ HttpURLConnection connection = getConnection(urlFragment, method);
- if (body != null) {
- connection.setDoOutput(true);
+ if (body != null) {
+ connection.setDoOutput(true);
+ }
+
+ connection.connect();
+
+ if (body != null) {
+ Log.w("PushServiceSocket", method + " -- " + body);
+ OutputStream out = connection.getOutputStream();
+ out.write(body.getBytes());
+ out.close();
+ }
+
+ if (connection.getResponseCode() == 413) {
+ connection.disconnect();
+ throw new RateLimitException("Rate limit exceeded: " + connection.getResponseCode());
+ }
+
+ if (connection.getResponseCode() == 401 || connection.getResponseCode() == 403) {
+ connection.disconnect();
+ throw new AuthorizationFailedException("Authorization failed!");
+ }
+
+ if (connection.getResponseCode() == 404) {
+ connection.disconnect();
+ throw new NotFoundException("Not found");
+ }
+
+ if (connection.getResponseCode() == 409) {
+ String response = Util.readFully(connection.getErrorStream());
+ throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
+ }
+
+ if (connection.getResponseCode() == 410) {
+ String response = Util.readFully(connection.getErrorStream());
+ throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
+ }
+
+ if (connection.getResponseCode() == 417) {
+ throw new ExpectationFailedException();
+ }
+
+ if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
+ throw new NonSuccessfulResponseCodeException("Bad response: " + connection.getResponseCode() +
+ " " + connection.getResponseMessage());
+ }
+
+ return connection;
+ } catch (IOException e) {
+ throw new PushNetworkException(e);
}
-
- connection.connect();
-
- if (body != null) {
- Log.w("PushServiceSocket", method + " -- " + body);
- OutputStream out = connection.getOutputStream();
- out.write(body.getBytes());
- out.close();
- }
-
- if (connection.getResponseCode() == 413) {
- connection.disconnect();
- throw new RateLimitException("Rate limit exceeded: " + connection.getResponseCode());
- }
-
- if (connection.getResponseCode() == 401 || connection.getResponseCode() == 403) {
- connection.disconnect();
- throw new AuthorizationFailedException("Authorization failed!");
- }
-
- if (connection.getResponseCode() == 404) {
- connection.disconnect();
- throw new NotFoundException("Not found");
- }
-
- if (connection.getResponseCode() == 409) {
- String response = Util.readFully(connection.getErrorStream());
- throw new MismatchedDevicesException(new Gson().fromJson(response, MismatchedDevices.class));
- }
-
- if (connection.getResponseCode() == 410) {
- String response = Util.readFully(connection.getErrorStream());
- throw new StaleDevicesException(new Gson().fromJson(response, StaleDevices.class));
- }
-
- if (connection.getResponseCode() == 417) {
- throw new ExpectationFailedException();
- }
-
- if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
- throw new IOException("Bad response: " + connection.getResponseCode() + " " + connection.getResponseMessage());
- }
-
- return connection;
}
private HttpURLConnection getConnection(String urlFragment, String method) throws IOException {
diff --git a/library/src/org/whispersystems/textsecure/push/RateLimitException.java b/library/src/org/whispersystems/textsecure/push/RateLimitException.java
deleted file mode 100644
index 873d1b3ae6..0000000000
--- a/library/src/org/whispersystems/textsecure/push/RateLimitException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.whispersystems.textsecure.push;
-
-
-import java.io.IOException;
-
-public class RateLimitException extends IOException {
- public RateLimitException(String s) {
- super(s);
- }
-}
diff --git a/library/src/org/whispersystems/textsecure/push/StaleDevicesException.java b/library/src/org/whispersystems/textsecure/push/StaleDevicesException.java
deleted file mode 100644
index 7b638aa02e..0000000000
--- a/library/src/org/whispersystems/textsecure/push/StaleDevicesException.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/AuthorizationFailedException.java b/library/src/org/whispersystems/textsecure/push/exceptions/AuthorizationFailedException.java
new file mode 100644
index 0000000000..9c16269298
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/AuthorizationFailedException.java
@@ -0,0 +1,7 @@
+package org.whispersystems.textsecure.push.exceptions;
+
+public class AuthorizationFailedException extends NonSuccessfulResponseCodeException {
+ public AuthorizationFailedException(String s) {
+ super(s);
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/ExpectationFailedException.java b/library/src/org/whispersystems/textsecure/push/exceptions/ExpectationFailedException.java
new file mode 100644
index 0000000000..8334f55117
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/ExpectationFailedException.java
@@ -0,0 +1,4 @@
+package org.whispersystems.textsecure.push.exceptions;
+
+public class ExpectationFailedException extends NonSuccessfulResponseCodeException {
+}
diff --git a/library/src/org/whispersystems/textsecure/push/MismatchedDevicesException.java b/library/src/org/whispersystems/textsecure/push/exceptions/MismatchedDevicesException.java
similarity index 57%
rename from library/src/org/whispersystems/textsecure/push/MismatchedDevicesException.java
rename to library/src/org/whispersystems/textsecure/push/exceptions/MismatchedDevicesException.java
index 9b34d4f5c5..cb02cc441a 100644
--- a/library/src/org/whispersystems/textsecure/push/MismatchedDevicesException.java
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/MismatchedDevicesException.java
@@ -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;
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/NonSuccessfulResponseCodeException.java b/library/src/org/whispersystems/textsecure/push/exceptions/NonSuccessfulResponseCodeException.java
new file mode 100644
index 0000000000..bb5773c38b
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/NonSuccessfulResponseCodeException.java
@@ -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);
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/NotFoundException.java b/library/src/org/whispersystems/textsecure/push/exceptions/NotFoundException.java
new file mode 100644
index 0000000000..c1e98e413f
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/NotFoundException.java
@@ -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);
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/PushNetworkException.java b/library/src/org/whispersystems/textsecure/push/exceptions/PushNetworkException.java
new file mode 100644
index 0000000000..c3ab4417cf
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/PushNetworkException.java
@@ -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);
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/RateLimitException.java b/library/src/org/whispersystems/textsecure/push/exceptions/RateLimitException.java
new file mode 100644
index 0000000000..c90de25f3b
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/RateLimitException.java
@@ -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);
+ }
+}
diff --git a/library/src/org/whispersystems/textsecure/push/exceptions/StaleDevicesException.java b/library/src/org/whispersystems/textsecure/push/exceptions/StaleDevicesException.java
new file mode 100644
index 0000000000..43f0333601
--- /dev/null
+++ b/library/src/org/whispersystems/textsecure/push/exceptions/StaleDevicesException.java
@@ -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;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
new file mode 100644
index 0000000000..06e1635508
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -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 .
+ */
+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());
+ }
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/ApplicationListener.java b/src/org/thoughtcrime/securesms/ApplicationListener.java
deleted file mode 100644
index 9ce69f3020..0000000000
--- a/src/org/thoughtcrime/securesms/ApplicationListener.java
+++ /dev/null
@@ -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 .
- */
-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();
- }
-
-}
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index 05f2b9618e..c2a0c90487 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -61,7 +61,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Trimmer;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.textsecure.crypto.MasterSecret;
-import org.whispersystems.textsecure.push.AuthorizationFailedException;
+import org.whispersystems.textsecure.push.exceptions.AuthorizationFailedException;
import org.whispersystems.textsecure.push.PushServiceSocket;
import java.io.IOException;
diff --git a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
index c66e574a71..43b2c7bc73 100644
--- a/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
+++ b/src/org/thoughtcrime/securesms/RegistrationProgressActivity.java
@@ -35,9 +35,9 @@ import org.thoughtcrime.securesms.service.RegistrationService;
import org.thoughtcrime.securesms.util.Dialogs;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
-import org.whispersystems.textsecure.push.ExpectationFailedException;
+import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
import org.whispersystems.textsecure.push.PushServiceSocket;
-import org.whispersystems.textsecure.push.RateLimitException;
+import org.whispersystems.textsecure.push.exceptions.RateLimitException;
import org.whispersystems.textsecure.util.PhoneNumberFormatter;
import org.whispersystems.textsecure.util.Util;
diff --git a/src/org/thoughtcrime/securesms/RoutingActivity.java b/src/org/thoughtcrime/securesms/RoutingActivity.java
index b2e04f270a..219dbd4bfa 100644
--- a/src/org/thoughtcrime/securesms/RoutingActivity.java
+++ b/src/org/thoughtcrime/securesms/RoutingActivity.java
@@ -9,8 +9,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
-import org.thoughtcrime.securesms.service.GcmRegistrationService;
-import org.thoughtcrime.securesms.service.PreKeyService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.textsecure.crypto.MasterSecret;
@@ -122,8 +120,6 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
final ConversationParameters parameters = getConversationParameters();
final Intent intent;
- scheduleRefreshActions();
-
if (isShareAction()) intent = getShareIntent(parameters);
else if (parameters.recipients != null) intent = getConversationIntent(parameters);
else intent = getConversationListIntent();
@@ -173,20 +169,20 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
return intent;
}
- private void scheduleRefreshActions() {
- if (TextSecurePreferences.isPushRegistered(this) &&
- TextSecurePreferences.getGcmRegistrationId(this) == null)
- {
- Intent intent = new Intent(this, GcmRegistrationService.class);
- startService(intent);
- }
-
- if (TextSecurePreferences.isPushRegistered(this) &&
- !TextSecurePreferences.isSignedPreKeyRegistered(this))
- {
- PreKeyService.initiateCreateSigned(this, masterSecret);
- }
- }
+// private void scheduleRefreshActions() {
+// if (TextSecurePreferences.isPushRegistered(this) &&
+// TextSecurePreferences.getGcmRegistrationId(this) == null)
+// {
+// Intent intent = new Intent(this, GcmRegistrationService.class);
+// startService(intent);
+// }
+//
+// if (TextSecurePreferences.isPushRegistered(this) &&
+// !TextSecurePreferences.isSignedPreKeyRegistered(this))
+// {
+// PreKeyService.initiateCreateSigned(this, masterSecret);
+// }
+// }
private int getApplicationState() {
if (!MasterSecretUtil.isPassphraseInitialized(this))
diff --git a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
index ada85addb7..c804052ba8 100644
--- a/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
+++ b/src/org/thoughtcrime/securesms/database/DatabaseFactory.java
@@ -55,7 +55,9 @@ public class DatabaseFactory {
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
- private static final int DATABASE_VERSION = 12;
+ private static final int INTRODUCED_DELIVERY_RECEIPTS = 13;
+ private static final int DATABASE_VERSION = 13;
+
private static final String DATABASE_NAME = "messages.db";
private static final Object lock = new Object();
@@ -702,6 +704,13 @@ public class DatabaseFactory {
db.execSQL("DROP TABLE push_backup;");
}
+ if (oldVersion < INTRODUCED_DELIVERY_RECEIPTS) {
+ db.execSQL("ALTER TABLE sms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;");
+ db.execSQL("ALTER TABLE mms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;");
+ db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS sms_date_sent_index ON sms (date_sent);");
+ db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);");
+ }
+
db.setTransactionSuccessful();
db.endTransaction();
}
diff --git a/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java b/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java
index 32260d1b3d..02952ebed2 100644
--- a/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsAddressDatabase.java
@@ -28,6 +28,8 @@ import ws.com.google.android.mms.pdu.EncodedStringValue;
import ws.com.google.android.mms.pdu.PduHeaders;
import java.io.UnsupportedEncodingException;
+import java.util.LinkedList;
+import java.util.List;
public class MmsAddressDatabase extends Database {
@@ -106,6 +108,25 @@ public class MmsAddressDatabase extends Database {
}
}
+ public List getAddressesForId(long messageId) {
+ List results = new LinkedList();
+ SQLiteDatabase database = databaseHelper.getReadableDatabase();
+ Cursor cursor = null;
+
+ try {
+ cursor = database.query(TABLE_NAME, null, MMS_ID + " = ?", new String[] {messageId+""}, null, null, null);
+
+ while (cursor != null && cursor.moveToNext()) {
+ results.add(cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return results;
+ }
+
public void deleteAddressesForId(long messageId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
index 955d7b9713..652b6dfee2 100644
--- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java
@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.LRUCache;
+import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.Trimmer;
import org.whispersystems.textsecure.util.Util;
@@ -122,13 +123,14 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + RETRIEVE_STATUS + " INTEGER, " +
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " INTEGER, " +
- DELIVERY_REPORT + " INTEGER);";
+ RECEIPT_COUNT + " INTEGER DEFAULT 0, " + DELIVERY_REPORT + " INTEGER);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS mms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
"CREATE INDEX IF NOT EXISTS mms_read_index ON " + TABLE_NAME + " (" + READ + ");",
"CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + THREAD_ID + ");",
- "CREATE INDEX IF NOT EXISTS mms_message_box_index ON " + TABLE_NAME + " (" + MESSAGE_BOX + ");"
+ "CREATE INDEX IF NOT EXISTS mms_message_box_index ON " + TABLE_NAME + " (" + MESSAGE_BOX + ");",
+ "CREATE INDEX IF NOT EXISTS mms_date_sent_index ON " + TABLE_NAME + " (" + DATE_SENT + ");"
};
private static final String[] MMS_PROJECTION = new String[] {
@@ -139,6 +141,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
+ RECEIPT_COUNT
};
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
@@ -166,6 +169,43 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return 0;
}
+ public void incrementDeliveryReceiptCount(String address, long timestamp) {
+ MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ Cursor cursor = null;
+
+ try {
+ cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID}, DATE_SENT + " = ?", new String[] {String.valueOf(timestamp / 1000)}, null, null, null, null);
+
+ while (cursor.moveToNext()) {
+ List addresses = addressDatabase.getAddressesForId(cursor.getLong(cursor.getColumnIndexOrThrow(ID)));
+
+ for (String storedAddress : addresses) {
+ try {
+ String ourAddress = org.thoughtcrime.securesms.util.Util.canonicalizeNumber(context, address);
+ String theirAddress = org.thoughtcrime.securesms.util.Util.canonicalizeNumber(context, storedAddress);
+
+ if (ourAddress.equals(theirAddress)) {
+ long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID));
+ long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID));
+
+ database.execSQL("UPDATE " + TABLE_NAME + " SET " +
+ RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " + ID + " = ?",
+ new String[] {String.valueOf(id)});
+
+ notifyConversationListeners(threadId);
+ }
+ } catch (InvalidNumberException e) {
+ Log.w("MmsDatabase", e);
+ }
+ }
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
public long getThreadIdForMessage(long id) {
String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?";
String[] sqlArgs = new String[] {id+""};
@@ -418,6 +458,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
+ long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
PduHeaders headers = getHeadersFromCursor(cursor);
addr.getAddressesForId(messageId, headers);
@@ -433,7 +474,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
Log.w("MmsDatabase", e);
}
- requests[i++] = new SendReq(headers, body, messageId, outboxType);
+ requests[i++] = new SendReq(headers, body, messageId, outboxType, timestamp);
}
return requests;
@@ -902,6 +943,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
+ int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
byte[]contentLocationBytes = null;
byte[]transactionIdBytes = null;
@@ -914,7 +956,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
- addressDeviceId, dateSent, dateReceived, threadId,
+ addressDeviceId, dateSent, dateReceived, receiptCount, threadId,
contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox);
}
@@ -927,6 +969,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
+ int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
DisplayRecord.Body body = getBody(cursor);
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
Recipients recipients = getRecipientsFor(address);
@@ -934,8 +977,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
ListenableFutureTask slideDeck = getSlideDeck(masterSecret, id);
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
- addressDeviceId, dateSent, dateReceived, threadId, body,
- slideDeck, partCount, box);
+ addressDeviceId, dateSent, dateReceived, receiptCount,
+ threadId, body, slideDeck, partCount, box);
}
private Recipients getRecipientsFor(String address) {
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
index e7b6022333..a451b2441b 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsColumns.java
@@ -10,6 +10,7 @@ public interface MmsSmsColumns {
public static final String BODY = "body";
public static final String ADDRESS = "address";
public static final String ADDRESS_DEVICE_ID = "address_device_id";
+ public static final String RECEIPT_COUNT = "delivery_receipt_count";
public static class Types {
protected static final long TOTAL_MASK = 0xFFFFFFFF;
diff --git a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
index 2e1e40723f..a05d8772ac 100644
--- a/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/MmsSmsDatabase.java
@@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.util.Log;
-import org.whispersystems.textsecure.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.model.MessageRecord;
+import org.whispersystems.textsecure.crypto.MasterSecret;
import java.util.HashSet;
import java.util.Set;
@@ -49,7 +49,7 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
- MmsDatabase.STATUS, TRANSPORT};
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
@@ -71,7 +71,7 @@ public class MmsSmsDatabase extends Database {
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
- MmsDatabase.STATUS, TRANSPORT};
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
@@ -89,7 +89,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
- MmsDatabase.STATUS, TRANSPORT};
+ MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
String selection = MmsSmsColumns.READ + " = 0";
@@ -104,6 +104,11 @@ public class MmsSmsDatabase extends Database {
return count;
}
+ public void incrementDeliveryReceiptCount(String address, long timestamp) {
+ DatabaseFactory.getSmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
+ DatabaseFactory.getMmsDatabase(context).incrementDeliveryReceiptCount(address, timestamp);
+ }
+
private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
@@ -112,7 +117,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
- TRANSPORT};
+ MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
@@ -121,7 +126,7 @@ public class MmsSmsDatabase extends Database {
MmsDatabase.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
- TRANSPORT};
+ MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
@@ -140,6 +145,7 @@ public class MmsSmsDatabase extends Database {
mmsColumnsPresent.add(MmsSmsColumns.BODY);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
+ mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
@@ -158,6 +164,7 @@ public class MmsSmsDatabase extends Database {
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
smsColumnsPresent.add(MmsSmsColumns.READ);
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
+ smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
smsColumnsPresent.add(SmsDatabase.TYPE);
smsColumnsPresent.add(SmsDatabase.SUBJECT);
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
diff --git a/src/org/thoughtcrime/securesms/database/SmsDatabase.java b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
index efafac79c8..3e98bbfd17 100644
--- a/src/org/thoughtcrime/securesms/database/SmsDatabase.java
+++ b/src/org/thoughtcrime/securesms/database/SmsDatabase.java
@@ -36,13 +36,18 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Trimmer;
+import org.whispersystems.textsecure.util.Base64;
+import org.whispersystems.textsecure.util.InvalidNumberException;
import org.whispersystems.textsecure.util.Util;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
+
/**
* Database for storage of SMS messages.
*
@@ -66,13 +71,15 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
THREAD_ID + " INTEGER, " + ADDRESS + " TEXT, " + ADDRESS_DEVICE_ID + " INTEGER DEFAULT 1, " + PERSON + " INTEGER, " +
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
- SUBJECT + " TEXT, " + BODY + " TEXT, " + SERVICE_CENTER + " TEXT);";
+ RECEIPT_COUNT + " INTEGER DEFAULT 0," + SUBJECT + " TEXT, " + BODY + " TEXT, " +
+ SERVICE_CENTER + " TEXT);";
public static final String[] CREATE_INDEXS = {
"CREATE INDEX IF NOT EXISTS sms_thread_id_index ON " + TABLE_NAME + " (" + THREAD_ID + ");",
"CREATE INDEX IF NOT EXISTS sms_read_index ON " + TABLE_NAME + " (" + READ + ");",
"CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON " + TABLE_NAME + "(" + READ + "," + THREAD_ID + ");",
- "CREATE INDEX IF NOT EXISTS sms_type_index ON " + TABLE_NAME + " (" + TYPE + ");"
+ "CREATE INDEX IF NOT EXISTS sms_type_index ON " + TABLE_NAME + " (" + TYPE + ");",
+ "CREATE INDEX IF NOT EXISTS sms_date_sent_index ON " + TABLE_NAME + " (" + DATE_SENT + ");"
};
private static final String[] MESSAGE_PROJECTION = new String[] {
@@ -80,7 +87,7 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
DATE_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
PROTOCOL, READ, STATUS, TYPE,
- REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER
+ REPLY_PATH_PRESENT, SUBJECT, BODY, SERVICE_CENTER, RECEIPT_COUNT
};
public SmsDatabase(Context context, SQLiteOpenHelper databaseHelper) {
@@ -240,6 +247,38 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
updateTypeBitmask(id, Types.BASE_TYPE_MASK, Types.BASE_SENT_FAILED_TYPE);
}
+ public void incrementDeliveryReceiptCount(String address, long timestamp) {
+ SQLiteDatabase database = databaseHelper.getWritableDatabase();
+ Cursor cursor = null;
+
+ try {
+ cursor = database.query(TABLE_NAME, new String[] {ID, THREAD_ID, ADDRESS},
+ DATE_SENT + " = ?", new String[] {String.valueOf(timestamp)},
+ null, null, null, null);
+
+ while (cursor.moveToNext()) {
+ try {
+ String theirAddress = canonicalizeNumber(context, address);
+ String ourAddress = canonicalizeNumber(context, cursor.getString(cursor.getColumnIndexOrThrow(ADDRESS)));
+
+ if (ourAddress.equals(theirAddress)) {
+ database.execSQL("UPDATE " + TABLE_NAME +
+ " SET " + RECEIPT_COUNT + " = " + RECEIPT_COUNT + " + 1 WHERE " +
+ ID + " = ?",
+ new String[] {String.valueOf(cursor.getLong(cursor.getColumnIndexOrThrow(ID)))});
+
+ notifyConversationListeners(cursor.getLong(cursor.getColumnIndexOrThrow(THREAD_ID)));
+ }
+ } catch (InvalidNumberException e) {
+ Log.w("SmsDatabase", e);
+ }
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ }
+
public void setMessagesRead(long threadId) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues();
@@ -539,13 +578,14 @@ public class SmsDatabase extends Database implements MmsSmsColumns {
long dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
+ int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
Recipients recipients = getRecipientsFor(address);
DisplayRecord.Body body = getBody(cursor);
return new SmsMessageRecord(context, messageId, body, recipients,
recipients.getPrimaryRecipient(),
addressDeviceId,
- dateSent, dateReceived, type,
+ dateSent, dateReceived, receiptCount, type,
threadId, status);
}
diff --git a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
index b11fac0562..1aff24577f 100644
--- a/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java
@@ -43,12 +43,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
Recipient individualRecipient, int recipientDeviceId,
- long dateSent, long dateReceived, long threadId, Body body,
+ long dateSent, long dateReceived, int deliveredCount,
+ long threadId, Body body,
ListenableFutureTask slideDeck,
int partCount, long mailbox)
{
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
- dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
+ dateSent, dateReceived, threadId, deliveredCount, DELIVERY_STATUS_NONE, mailbox);
this.context = context.getApplicationContext();
this.partCount = partCount;
diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
index 4414a4059b..8ad9eb782d 100644
--- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java
@@ -48,17 +48,19 @@ public abstract class MessageRecord extends DisplayRecord {
private final int recipientDeviceId;
private final long id;
private final int deliveryStatus;
+ private final int receiptCount;
MessageRecord(Context context, long id, Body body, Recipients recipients,
Recipient individualRecipient, int recipientDeviceId,
- long dateSent, long dateReceived,
- long threadId, int deliveryStatus, long type)
+ long dateSent, long dateReceived, long threadId,
+ int deliveryStatus, int receiptCount, long type)
{
super(context, body, recipients, dateSent, dateReceived, threadId, type);
this.id = id;
this.individualRecipient = individualRecipient;
this.recipientDeviceId = recipientDeviceId;
this.deliveryStatus = deliveryStatus;
+ this.receiptCount = receiptCount;
}
public abstract boolean isMms();
@@ -110,7 +112,7 @@ public abstract class MessageRecord extends DisplayRecord {
}
public boolean isDelivered() {
- return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED;
+ return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED || receiptCount > 0;
}
public boolean isPush() {
diff --git a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
index 33adfaa4b6..097a213d0f 100644
--- a/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java
@@ -42,12 +42,12 @@ public class NotificationMmsMessageRecord extends MessageRecord {
public NotificationMmsMessageRecord(Context context, long id, Recipients recipients,
Recipient individualRecipient, int recipientDeviceId,
- long dateSent, long dateReceived, long threadId,
- byte[] contentLocation, long messageSize, long expiry,
- int status, byte[] transactionId, long mailbox)
+ long dateSent, long dateReceived, int receiptCount,
+ long threadId, byte[] contentLocation, long messageSize,
+ long expiry, int status, byte[] transactionId, long mailbox)
{
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
- dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, mailbox);
+ dateSent, dateReceived, threadId, DELIVERY_STATUS_NONE, receiptCount, mailbox);
this.contentLocation = contentLocation;
this.messageSize = messageSize;
diff --git a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
index 26d5b0a291..3777232980 100644
--- a/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
+++ b/src/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java
@@ -43,11 +43,12 @@ public class SmsMessageRecord extends MessageRecord {
Recipient individualRecipient,
int recipientDeviceId,
long dateSent, long dateReceived,
+ int receiptCount,
long type, long threadId,
int status)
{
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
- dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type);
+ dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type);
}
public long getType() {
diff --git a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java
index 0dc21c5546..4086e8adc4 100644
--- a/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java
+++ b/src/org/thoughtcrime/securesms/gcm/GcmBroadcastReceiver.java
@@ -6,7 +6,10 @@ import android.content.Intent;
import android.util.Log;
import com.google.android.gms.gcm.GoogleCloudMessaging;
+import com.path.android.jobqueue.JobManager;
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
import org.thoughtcrime.securesms.service.SendReceiveService;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.libaxolotl.InvalidVersionException;
@@ -31,38 +34,47 @@ public class GcmBroadcastReceiver extends BroadcastReceiver {
if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
Log.w(TAG, "GCM message...");
- try {
- String data = intent.getStringExtra("message");
-
- if (Util.isEmpty(data))
- return;
-
- if (!TextSecurePreferences.isPushRegistered(context)) {
- Log.w(TAG, "Not push registered!");
- return;
- }
-
- String sessionKey = TextSecurePreferences.getSignalingKey(context);
- IncomingEncryptedPushMessage encryptedMessage = new IncomingEncryptedPushMessage(data, sessionKey);
- IncomingPushMessage message = encryptedMessage.getIncomingPushMessage();
-
- if (!isActiveNumber(context, message.getSource())) {
- Directory directory = Directory.getInstance(context);
- ContactTokenDetails contactTokenDetails = new ContactTokenDetails();
- contactTokenDetails.setNumber(message.getSource());
-
- directory.setNumber(contactTokenDetails, true);
- }
-
- Intent service = new Intent(context, SendReceiveService.class);
- service.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
- service.putExtra("message", message);
- context.startService(service);
- } catch (IOException e) {
- Log.w(TAG, e);
- } catch (InvalidVersionException e) {
- Log.w(TAG, e);
+ if (!TextSecurePreferences.isPushRegistered(context)) {
+ Log.w(TAG, "Not push registered!");
+ return;
}
+
+ String messageData = intent.getStringExtra("message");
+ String receiptData = intent.getStringExtra("receipt");
+
+ if (!Util.isEmpty(messageData)) handleReceivedMessage(context, messageData);
+ else if (!Util.isEmpty(receiptData)) handleReceivedMessage(context, receiptData);
+ }
+ }
+
+ private void handleReceivedMessage(Context context, String data) {
+ try {
+ String sessionKey = TextSecurePreferences.getSignalingKey(context);
+ IncomingEncryptedPushMessage encrypted = new IncomingEncryptedPushMessage(data, sessionKey);
+ IncomingPushMessage message = encrypted.getIncomingPushMessage();
+
+ if (!isActiveNumber(context, message.getSource())) {
+ Directory directory = Directory.getInstance(context);
+ ContactTokenDetails contactTokenDetails = new ContactTokenDetails();
+ contactTokenDetails.setNumber(message.getSource());
+
+ directory.setNumber(contactTokenDetails, true);
+ }
+
+ Intent receiveService = new Intent(context, SendReceiveService.class);
+ receiveService.setAction(SendReceiveService.RECEIVE_PUSH_ACTION);
+ receiveService.putExtra("message", message);
+ context.startService(receiveService);
+
+ if (!message.isReceipt()) {
+ JobManager jobManager = ApplicationContext.getInstance(context).getJobManager();
+ jobManager.addJob(new DeliveryReceiptJob(message.getSource(), message.getTimestampMillis(),
+ message.getRelay()));
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ } catch (InvalidVersionException e) {
+ Log.w(TAG, e);
}
}
diff --git a/src/org/thoughtcrime/securesms/jobs/ContextInjector.java b/src/org/thoughtcrime/securesms/jobs/ContextInjector.java
new file mode 100644
index 0000000000..3a5cb82217
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/ContextInjector.java
@@ -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);
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/ContextJob.java b/src/org/thoughtcrime/securesms/jobs/ContextJob.java
new file mode 100644
index 0000000000..cebcd21546
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/ContextJob.java
@@ -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;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java b/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java
new file mode 100644
index 0000000000..5c2d2e92e5
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java
@@ -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;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java b/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
new file mode 100644
index 0000000000..97369f9dbb
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
@@ -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;
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/JobLogger.java b/src/org/thoughtcrime/securesms/jobs/JobLogger.java
new file mode 100644
index 0000000000..f78fe2e921
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/JobLogger.java
@@ -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));
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/jobs/Priorities.java b/src/org/thoughtcrime/securesms/jobs/Priorities.java
new file mode 100644
index 0000000000..347fb2b736
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/jobs/Priorities.java
@@ -0,0 +1,8 @@
+package org.thoughtcrime.securesms.jobs;
+
+public class Priorities {
+
+ public static final int NORMAL = 500;
+ public static final int HIGH = 1000;
+
+}
diff --git a/src/org/thoughtcrime/securesms/service/PushDownloader.java b/src/org/thoughtcrime/securesms/service/PushDownloader.java
index 967c80bdff..262258a7be 100644
--- a/src/org/thoughtcrime/securesms/service/PushDownloader.java
+++ b/src/org/thoughtcrime/securesms/service/PushDownloader.java
@@ -6,7 +6,6 @@ import android.content.Intent;
import android.util.Log;
import android.util.Pair;
-import org.thoughtcrime.securesms.Release;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.EncryptingPartDatabase;
import org.thoughtcrime.securesms.database.PartDatabase;
@@ -16,7 +15,7 @@ import org.whispersystems.libaxolotl.InvalidMessageException;
import org.whispersystems.textsecure.crypto.AttachmentCipherInputStream;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
-import org.whispersystems.textsecure.push.NotFoundException;
+import org.whispersystems.textsecure.push.exceptions.NotFoundException;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.Base64;
diff --git a/src/org/thoughtcrime/securesms/service/PushReceiver.java b/src/org/thoughtcrime/securesms/service/PushReceiver.java
index 586e7cc269..13d5a55c54 100644
--- a/src/org/thoughtcrime/securesms/service/PushReceiver.java
+++ b/src/org/thoughtcrime/securesms/service/PushReceiver.java
@@ -50,6 +50,8 @@ import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageCo
public class PushReceiver {
+ private static final String TAG = PushReceiver.class.getSimpleName();
+
public static final int RESULT_OK = 0;
public static final int RESULT_NO_SESSION = 1;
public static final int RESULT_DECRYPT_FAILED = 2;
@@ -97,7 +99,9 @@ public class PushReceiver {
if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message);
else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(masterSecret, message);
- else handleReceivedMessage(masterSecret, message, false);
+ else if (message.isReceipt()) handleReceivedReceipt(message);
+ else if (message.isPlaintext()) handleReceivedMessage(masterSecret, message, false);
+ else Log.w(TAG, "Received push of unknown type!");
}
private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) {
@@ -130,21 +134,21 @@ public class PushReceiver {
handleReceivedMessage(masterSecret, bundledMessage, true);
} catch (InvalidVersionException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
handleReceivedCorruptedKey(masterSecret, message, true);
} catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException |
RecipientFormattingException | LegacyMessageException e)
{
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
handleReceivedCorruptedKey(masterSecret, message, false);
} catch (DuplicateMessageException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
handleReceivedDuplicateMessage(message);
} catch (NoSessionException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
handleReceivedMessageForNoSession(masterSecret, message);
} catch (UntrustedIdentityException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
String encoded = Base64.encodeBytes(message.getBody());
IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
@@ -163,24 +167,31 @@ public class PushReceiver {
PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody());
if (secure && (messageContent.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0) {
- Log.w("PushReceiver", "Received end session message...");
+ Log.w(TAG, "Received end session message...");
handleEndSessionMessage(masterSecret, message, messageContent);
} else if (messageContent.hasGroup() && messageContent.getGroup().getType().getNumber() != Type.DELIVER_VALUE) {
- Log.w("PushReceiver", "Received push group message...");
+ Log.w(TAG, "Received push group message...");
groupReceiver.process(masterSecret, message, messageContent, secure);
} else if (messageContent.getAttachmentsCount() > 0) {
- Log.w("PushReceiver", "Received push media message...");
+ Log.w(TAG, "Received push media message...");
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
} else {
- Log.w("PushReceiver", "Received push text message...");
+ Log.w(TAG, "Received push text message...");
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
}
} catch (InvalidProtocolBufferException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
handleReceivedCorruptedMessage(masterSecret, message, secure);
}
}
+ private void handleReceivedReceipt(IncomingPushMessage message)
+ {
+ Log.w("PushReceiver", String.format("Received receipt: (XXXXX, %d)", message.getTimestampMillis()));
+ DatabaseFactory.getMmsSmsDatabase(context).incrementDeliveryReceiptCount(message.getSource(),
+ message.getTimestampMillis());
+ }
+
private void handleEndSessionMessage(MasterSecret masterSecret,
IncomingPushMessage message,
PushMessageContent messageContent)
@@ -200,7 +211,7 @@ public class PushReceiver {
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
} catch (RecipientFormattingException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
}
}
@@ -231,7 +242,7 @@ public class PushReceiver {
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
} catch (MmsException e) {
- Log.w("PushReceiver", e);
+ Log.w(TAG, e);
// XXX
}
}
@@ -267,7 +278,7 @@ public class PushReceiver {
}
private void handleReceivedDuplicateMessage(IncomingPushMessage message) {
- Log.w("PushReceiver", "Received duplicate message: " + message.getSource() + " , " + message.getSourceDevice());
+ Log.w(TAG, "Received duplicate message: " + message.getSource() + " , " + message.getSourceDevice());
}
private void handleReceivedCorruptedKey(MasterSecret masterSecret,
diff --git a/src/org/thoughtcrime/securesms/service/RegistrationService.java b/src/org/thoughtcrime/securesms/service/RegistrationService.java
index 7d80fa4baa..c084654951 100644
--- a/src/org/thoughtcrime/securesms/service/RegistrationService.java
+++ b/src/org/thoughtcrime/securesms/service/RegistrationService.java
@@ -23,7 +23,7 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.PreKeyUtil;
-import org.whispersystems.textsecure.push.ExpectationFailedException;
+import org.whispersystems.textsecure.push.exceptions.ExpectationFailedException;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.util.Util;
diff --git a/src/org/thoughtcrime/securesms/transport/PushTransport.java b/src/org/thoughtcrime/securesms/transport/PushTransport.java
index c856d9c163..f993260f2f 100644
--- a/src/org/thoughtcrime/securesms/transport/PushTransport.java
+++ b/src/org/thoughtcrime/securesms/transport/PushTransport.java
@@ -43,7 +43,7 @@ import org.whispersystems.textsecure.crypto.AttachmentCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.crypto.TransportDetails;
import org.whispersystems.textsecure.push.MismatchedDevices;
-import org.whispersystems.textsecure.push.MismatchedDevicesException;
+import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
import org.whispersystems.textsecure.push.OutgoingPushMessage;
import org.whispersystems.textsecure.push.OutgoingPushMessageList;
import org.whispersystems.textsecure.push.PushAddress;
@@ -54,7 +54,7 @@ import org.whispersystems.textsecure.push.PushMessageProtos.PushMessageContent;
import org.whispersystems.textsecure.push.PushServiceSocket;
import org.whispersystems.textsecure.push.PushTransportDetails;
import org.whispersystems.textsecure.push.StaleDevices;
-import org.whispersystems.textsecure.push.StaleDevicesException;
+import org.whispersystems.textsecure.push.exceptions.StaleDevicesException;
import org.whispersystems.textsecure.push.UnregisteredUserException;
import org.whispersystems.textsecure.storage.SessionUtil;
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
@@ -92,7 +92,7 @@ public class PushTransport extends BaseTransport {
PushServiceSocket socket = PushServiceSocketFactory.create(context);
byte[] plaintext = getPlaintextMessage(message);
- deliver(socket, recipient, threadId, plaintext);
+ deliver(socket, recipient, message.getDateSent(), threadId, plaintext);
if (message.isEndSession()) {
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
@@ -129,7 +129,7 @@ public class PushTransport extends BaseTransport {
for (Recipient recipient : recipients.getRecipientsList()) {
try {
- deliver(socket, recipient, threadId, plaintext);
+ deliver(socket, recipient, message.getSentTimestamp(), threadId, plaintext);
} catch (UntrustedIdentityException e) {
Log.w("PushTransport", e);
untrustedIdentities.add(e);
@@ -144,13 +144,14 @@ public class PushTransport extends BaseTransport {
}
}
- private void deliver(PushServiceSocket socket, Recipient recipient, long threadId, byte[] plaintext)
+ private void deliver(PushServiceSocket socket, Recipient recipient, long timestamp,
+ long threadId, byte[] plaintext)
throws IOException, InvalidNumberException, UntrustedIdentityException
{
for (int i=0;i<3;i++) {
try {
- OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId,
- recipient, plaintext);
+ OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId, recipient,
+ timestamp, plaintext);
socket.sendMessage(messages);
return;
@@ -300,7 +301,8 @@ public class PushTransport extends BaseTransport {
}
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
- Recipient recipient, byte[] plaintext)
+ Recipient recipient, long timestamp,
+ byte[] plaintext)
throws IOException, InvalidNumberException, UntrustedIdentityException
{
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
@@ -319,7 +321,7 @@ public class PushTransport extends BaseTransport {
messages.add(new OutgoingPushMessage(device, body));
}
- return new OutgoingPushMessageList(e164number, masterDevice.getRelay(), messages);
+ return new OutgoingPushMessageList(e164number, timestamp, masterDevice.getRelay(), messages);
}
private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId,
diff --git a/src/ws/com/google/android/mms/pdu/SendReq.java b/src/ws/com/google/android/mms/pdu/SendReq.java
index d4f824e29e..60a5d47aa8 100644
--- a/src/ws/com/google/android/mms/pdu/SendReq.java
+++ b/src/ws/com/google/android/mms/pdu/SendReq.java
@@ -25,6 +25,7 @@ public class SendReq extends MultimediaMessagePdu {
private static final String TAG = "SendReq";
private long databaseMessageId;
private long messageBox;
+ private long timestamp;
public SendReq() {
super();
@@ -90,11 +91,12 @@ public class SendReq extends MultimediaMessagePdu {
super(headers, body);
}
- public SendReq(PduHeaders headers, PduBody body, long messageId, long messageBox)
+ public SendReq(PduHeaders headers, PduBody body, long messageId, long messageBox, long timestamp)
{
super(headers, body);
this.databaseMessageId = messageId;
this.messageBox = messageBox;
+ this.timestamp = timestamp;
}
public long getDatabaseMessageBox() {
@@ -105,6 +107,10 @@ public class SendReq extends MultimediaMessagePdu {
return databaseMessageId;
}
+ public long getSentTimestamp() {
+ return timestamp;
+ }
+
/**
* Get Bcc value.
*