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. *