mirror of
https://github.com/oxen-io/session-android.git
synced 2025-01-11 23:23:39 +00:00
Implement delivery receipts.
1) Support a "receipt" push message type. 2) Identify messages by timestamp. 3) Introduce a JobManager to handle the queue for network dependent jobs.
This commit is contained in:
parent
8d6b9ae43e
commit
36ec1d84a1
@ -37,7 +37,7 @@
|
|||||||
android:protectionLevel="signature" />
|
android:protectionLevel="signature" />
|
||||||
<uses-permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE" />
|
<uses-permission android:name="org.thoughtcrime.securesms.permission.C2D_MESSAGE" />
|
||||||
|
|
||||||
<application android:name="org.thoughtcrime.securesms.ApplicationListener"
|
<application android:name=".ApplicationContext"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/TextSecure.LightTheme">
|
android:theme="@style/TextSecure.LightTheme">
|
||||||
|
@ -36,6 +36,7 @@ dependencies {
|
|||||||
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
|
||||||
compile 'org.w3c:smil:1.0.0'
|
compile 'org.w3c:smil:1.0.0'
|
||||||
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
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'
|
androidTestCompile 'com.squareup:fest-android:1.0.8'
|
||||||
|
|
||||||
@ -56,6 +57,8 @@ dependencyVerification {
|
|||||||
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
|
'com.madgag:sc-light-jdk15on:931f39d351429fb96c2f749e7ecb1a256a8ebbf5edca7995c9cc085b94d1841d',
|
||||||
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
|
||||||
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
|
||||||
|
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
|
||||||
|
'com.path:android-priority-jobqueue:af8d0dc930c3640518e9548ec887cf7871ab5e3c1ea634a4553dd5c30dd6e4a8'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
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.madgag:sc-light-jdk15on:1.47.0.2'
|
||||||
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
compile 'com.googlecode.libphonenumber:libphonenumber:6.1'
|
||||||
compile 'org.whispersystems:gson:2.2.4'
|
compile 'org.whispersystems:gson:2.2.4'
|
||||||
|
@ -10,6 +10,7 @@ message IncomingPushMessageSignal {
|
|||||||
KEY_EXCHANGE = 2;
|
KEY_EXCHANGE = 2;
|
||||||
PREKEY_BUNDLE = 3;
|
PREKEY_BUNDLE = 3;
|
||||||
PLAINTEXT = 4;
|
PLAINTEXT = 4;
|
||||||
|
RECEIPT = 5;
|
||||||
}
|
}
|
||||||
optional Type type = 1;
|
optional Type type = 1;
|
||||||
optional string source = 2;
|
optional string source = 2;
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class AuthorizationFailedException extends IOException {
|
|
||||||
public AuthorizationFailedException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class ExpectationFailedException extends IOException {
|
|
||||||
}
|
|
@ -138,4 +138,12 @@ public class IncomingPushMessage implements Parcelable {
|
|||||||
public boolean isPreKeyBundle() {
|
public boolean isPreKeyBundle() {
|
||||||
return getType() == IncomingPushMessageSignal.Type.PREKEY_BUNDLE_VALUE;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class NotFoundException extends IOException {
|
|
||||||
public NotFoundException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
package org.whispersystems.textsecure.push;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class OutgoingPushMessageList {
|
public class OutgoingPushMessageList {
|
||||||
@ -9,9 +8,14 @@ public class OutgoingPushMessageList {
|
|||||||
|
|
||||||
private String relay;
|
private String relay;
|
||||||
|
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
private List<OutgoingPushMessage> messages;
|
private List<OutgoingPushMessage> messages;
|
||||||
|
|
||||||
public OutgoingPushMessageList(String destination, String relay, List<OutgoingPushMessage> messages) {
|
public OutgoingPushMessageList(String destination, long timestamp, String relay,
|
||||||
|
List<OutgoingPushMessage> messages)
|
||||||
|
{
|
||||||
|
this.timestamp = timestamp;
|
||||||
this.destination = destination;
|
this.destination = destination;
|
||||||
this.relay = relay;
|
this.relay = relay;
|
||||||
this.messages = messages;
|
this.messages = messages;
|
||||||
@ -28,4 +32,8 @@ public class OutgoingPushMessageList {
|
|||||||
public String getRelay() {
|
public String getRelay() {
|
||||||
return relay;
|
return relay;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,6 +240,10 @@ public final class PushMessageProtos {
|
|||||||
* <code>PLAINTEXT = 4;</code>
|
* <code>PLAINTEXT = 4;</code>
|
||||||
*/
|
*/
|
||||||
PLAINTEXT(4, 4),
|
PLAINTEXT(4, 4),
|
||||||
|
/**
|
||||||
|
* <code>RECEIPT = 5;</code>
|
||||||
|
*/
|
||||||
|
RECEIPT(5, 5),
|
||||||
;
|
;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -262,6 +266,10 @@ public final class PushMessageProtos {
|
|||||||
* <code>PLAINTEXT = 4;</code>
|
* <code>PLAINTEXT = 4;</code>
|
||||||
*/
|
*/
|
||||||
public static final int PLAINTEXT_VALUE = 4;
|
public static final int PLAINTEXT_VALUE = 4;
|
||||||
|
/**
|
||||||
|
* <code>RECEIPT = 5;</code>
|
||||||
|
*/
|
||||||
|
public static final int RECEIPT_VALUE = 5;
|
||||||
|
|
||||||
|
|
||||||
public final int getNumber() { return value; }
|
public final int getNumber() { return value; }
|
||||||
@ -273,6 +281,7 @@ public final class PushMessageProtos {
|
|||||||
case 2: return KEY_EXCHANGE;
|
case 2: return KEY_EXCHANGE;
|
||||||
case 3: return PREKEY_BUNDLE;
|
case 3: return PREKEY_BUNDLE;
|
||||||
case 4: return PLAINTEXT;
|
case 4: return PLAINTEXT;
|
||||||
|
case 5: return RECEIPT;
|
||||||
default: return null;
|
default: return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4079,28 +4088,28 @@ public final class PushMessageProtos {
|
|||||||
static {
|
static {
|
||||||
java.lang.String[] descriptorData = {
|
java.lang.String[] descriptorData = {
|
||||||
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
|
"\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" +
|
"ype\030\001 \001(\0162*.textsecure.IncomingPushMessa" +
|
||||||
"geSignal.Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceD" +
|
"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" +
|
"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" +
|
"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" +
|
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\022\013\n\007RECEI" +
|
||||||
"MessageContent\022\014\n\004body\030\001 \001(\t\022E\n\013attachme" +
|
"PT\020\005\"\207\004\n\022PushMessageContent\022\014\n\004body\030\001 \001(" +
|
||||||
"nts\030\002 \003(\01320.textsecure.PushMessageConten",
|
"\t\022E\n\013attachments\030\002 \003(\01320.textsecure.Push",
|
||||||
"t.AttachmentPointer\022:\n\005group\030\003 \001(\0132+.tex" +
|
"MessageContent.AttachmentPointer\022:\n\005grou" +
|
||||||
"tsecure.PushMessageContent.GroupContext\022" +
|
"p\030\003 \001(\0132+.textsecure.PushMessageContent." +
|
||||||
"\r\n\005flags\030\004 \001(\r\032A\n\021AttachmentPointer\022\n\n\002i" +
|
"GroupContext\022\r\n\005flags\030\004 \001(\r\032A\n\021Attachmen" +
|
||||||
"d\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(" +
|
"tPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002 \001(" +
|
||||||
"\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>\n\004type\030\002" +
|
"\t\022\013\n\003key\030\003 \001(\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 " +
|
||||||
" \001(\01620.textsecure.PushMessageContent.Gro" +
|
"\001(\014\022>\n\004type\030\002 \001(\01620.textsecure.PushMessa" +
|
||||||
"upContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030" +
|
"geContent.GroupContext.Type\022\014\n\004name\030\003 \001(" +
|
||||||
"\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecure.PushM" +
|
"\t\022\017\n\007members\030\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.tex" +
|
||||||
"essageContent.AttachmentPointer\"6\n\004Type\022" +
|
"tsecure.PushMessageContent.AttachmentPoi" +
|
||||||
"\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n",
|
"nter\"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n",
|
||||||
"\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SESSION\020\001B7\n\"org" +
|
"\007DELIVER\020\002\022\010\n\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SES" +
|
||||||
".whispersystems.textsecure.pushB\021PushMes" +
|
"SION\020\001B7\n\"org.whispersystems.textsecure." +
|
||||||
"sageProtos"
|
"pushB\021PushMessageProtos"
|
||||||
};
|
};
|
||||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||||
|
@ -25,9 +25,17 @@ import com.google.thoughtcrimegson.JsonParseException;
|
|||||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
import org.whispersystems.libaxolotl.IdentityKey;
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
|
||||||
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||||
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
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.Base64;
|
||||||
import org.whispersystems.textsecure.util.BlacklistingTrustManager;
|
import org.whispersystems.textsecure.util.BlacklistingTrustManager;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
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_TOKENS_PATH = "/v1/directory/tokens";
|
||||||
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
|
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
|
||||||
private static final String MESSAGE_PATH = "/v1/messages/%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 String ATTACHMENT_PATH = "/v1/attachments/%s";
|
||||||
|
|
||||||
private static final boolean ENFORCE_SSL = true;
|
private static final boolean ENFORCE_SSL = true;
|
||||||
@ -109,6 +118,16 @@ public class PushServiceSocket {
|
|||||||
"PUT", new Gson().toJson(signalingKeyEntity));
|
"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 {
|
public void registerGcmId(String gcmRegistrationId) throws IOException {
|
||||||
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
|
GcmRegistrationId registration = new GcmRegistrationId(gcmRegistrationId);
|
||||||
makeRequest(REGISTER_GCM_PATH, "PUT", new Gson().toJson(registration));
|
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)
|
private String makeRequest(String urlFragment, String method, String body)
|
||||||
throws IOException
|
throws NonSuccessfulResponseCodeException, PushNetworkException
|
||||||
{
|
{
|
||||||
HttpURLConnection connection = makeBaseRequest(urlFragment, method, body);
|
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)
|
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) {
|
if (body != null) {
|
||||||
connection.setDoOutput(true);
|
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 {
|
private HttpURLConnection getConnection(String urlFragment, String method) throws IOException {
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class RateLimitException extends IOException {
|
|
||||||
public RateLimitException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class StaleDevicesException extends IOException {
|
|
||||||
|
|
||||||
private final StaleDevices staleDevices;
|
|
||||||
|
|
||||||
public StaleDevicesException(StaleDevices staleDevices) {
|
|
||||||
this.staleDevices = staleDevices;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StaleDevices getStaleDevices() {
|
|
||||||
return staleDevices;
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,7 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
public class AuthorizationFailedException extends NonSuccessfulResponseCodeException {
|
||||||
|
public AuthorizationFailedException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
public class ExpectationFailedException extends NonSuccessfulResponseCodeException {
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package org.whispersystems.textsecure.push;
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
import java.io.IOException;
|
import org.whispersystems.textsecure.push.MismatchedDevices;
|
||||||
|
|
||||||
public class MismatchedDevicesException extends IOException {
|
public class MismatchedDevicesException extends NonSuccessfulResponseCodeException {
|
||||||
|
|
||||||
private final MismatchedDevices mismatchedDevices;
|
private final MismatchedDevices mismatchedDevices;
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class NonSuccessfulResponseCodeException extends IOException {
|
||||||
|
|
||||||
|
public NonSuccessfulResponseCodeException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NonSuccessfulResponseCodeException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
|
|
||||||
|
public class NotFoundException extends NonSuccessfulResponseCodeException {
|
||||||
|
public NotFoundException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class PushNetworkException extends IOException {
|
||||||
|
public PushNetworkException(Exception exception) {
|
||||||
|
super(exception);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
|
|
||||||
|
public class RateLimitException extends NonSuccessfulResponseCodeException {
|
||||||
|
public RateLimitException(String s) {
|
||||||
|
super(s);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package org.whispersystems.textsecure.push.exceptions;
|
||||||
|
|
||||||
|
import org.whispersystems.textsecure.push.StaleDevices;
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
|
|
||||||
|
public class StaleDevicesException extends NonSuccessfulResponseCodeException {
|
||||||
|
|
||||||
|
private final StaleDevices staleDevices;
|
||||||
|
|
||||||
|
public StaleDevicesException(StaleDevices staleDevices) {
|
||||||
|
this.staleDevices = staleDevices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StaleDevices getStaleDevices() {
|
||||||
|
return staleDevices;
|
||||||
|
}
|
||||||
|
}
|
80
src/org/thoughtcrime/securesms/ApplicationContext.java
Normal file
80
src/org/thoughtcrime/securesms/ApplicationContext.java
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2013 Open Whisper Systems
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.path.android.jobqueue.JobManager;
|
||||||
|
import com.path.android.jobqueue.config.Configuration;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
||||||
|
import org.thoughtcrime.securesms.jobs.ContextInjector;
|
||||||
|
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
|
||||||
|
import org.thoughtcrime.securesms.jobs.JobLogger;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be called once when the TextSecure process is created.
|
||||||
|
*
|
||||||
|
* We're using this as an insertion point to patch up the Android PRNG disaster,
|
||||||
|
* to initialize the job manager, and to check for GCM registration freshness.
|
||||||
|
*
|
||||||
|
* @author Moxie Marlinspike
|
||||||
|
*/
|
||||||
|
public class ApplicationContext extends Application {
|
||||||
|
|
||||||
|
private JobManager jobManager;
|
||||||
|
|
||||||
|
public static ApplicationContext getInstance(Context context) {
|
||||||
|
return (ApplicationContext)context.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
initializeRandomNumberFix();
|
||||||
|
initializeJobManager();
|
||||||
|
initializeGcmCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JobManager getJobManager() {
|
||||||
|
return jobManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeRandomNumberFix() {
|
||||||
|
PRNGFixes.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeJobManager() {
|
||||||
|
Configuration configuration = new Configuration.Builder(this)
|
||||||
|
.minConsumerCount(1)
|
||||||
|
.injector(new ContextInjector(this))
|
||||||
|
.customLogger(new JobLogger())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
this.jobManager = new JobManager(this, configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeGcmCheck() {
|
||||||
|
if (TextSecurePreferences.isPushRegistered(this) &&
|
||||||
|
TextSecurePreferences.getGcmRegistrationId(this) == null)
|
||||||
|
{
|
||||||
|
this.jobManager.addJob(new GcmRefreshJob());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.PRNGFixes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Will be called once when the TextSecure process is created.
|
|
||||||
*
|
|
||||||
* We're using this as an insertion point to patch up the Android PRNG disaster.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
public class ApplicationListener extends Application {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
PRNGFixes.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -61,7 +61,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|||||||
import org.thoughtcrime.securesms.util.Trimmer;
|
import org.thoughtcrime.securesms.util.Trimmer;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
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 org.whispersystems.textsecure.push.PushServiceSocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -35,9 +35,9 @@ import org.thoughtcrime.securesms.service.RegistrationService;
|
|||||||
import org.thoughtcrime.securesms.util.Dialogs;
|
import org.thoughtcrime.securesms.util.Dialogs;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
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.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.PhoneNumberFormatter;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
|
@ -9,8 +9,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
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.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
|
||||||
@ -122,8 +120,6 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
|
|||||||
final ConversationParameters parameters = getConversationParameters();
|
final ConversationParameters parameters = getConversationParameters();
|
||||||
final Intent intent;
|
final Intent intent;
|
||||||
|
|
||||||
scheduleRefreshActions();
|
|
||||||
|
|
||||||
if (isShareAction()) intent = getShareIntent(parameters);
|
if (isShareAction()) intent = getShareIntent(parameters);
|
||||||
else if (parameters.recipients != null) intent = getConversationIntent(parameters);
|
else if (parameters.recipients != null) intent = getConversationIntent(parameters);
|
||||||
else intent = getConversationListIntent();
|
else intent = getConversationListIntent();
|
||||||
@ -173,20 +169,20 @@ public class RoutingActivity extends PassphraseRequiredSherlockActivity {
|
|||||||
return intent;
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleRefreshActions() {
|
// private void scheduleRefreshActions() {
|
||||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
// if (TextSecurePreferences.isPushRegistered(this) &&
|
||||||
TextSecurePreferences.getGcmRegistrationId(this) == null)
|
// TextSecurePreferences.getGcmRegistrationId(this) == null)
|
||||||
{
|
// {
|
||||||
Intent intent = new Intent(this, GcmRegistrationService.class);
|
// Intent intent = new Intent(this, GcmRegistrationService.class);
|
||||||
startService(intent);
|
// startService(intent);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (TextSecurePreferences.isPushRegistered(this) &&
|
// if (TextSecurePreferences.isPushRegistered(this) &&
|
||||||
!TextSecurePreferences.isSignedPreKeyRegistered(this))
|
// !TextSecurePreferences.isSignedPreKeyRegistered(this))
|
||||||
{
|
// {
|
||||||
PreKeyService.initiateCreateSigned(this, masterSecret);
|
// PreKeyService.initiateCreateSigned(this, masterSecret);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
private int getApplicationState() {
|
private int getApplicationState() {
|
||||||
if (!MasterSecretUtil.isPassphraseInitialized(this))
|
if (!MasterSecretUtil.isPassphraseInitialized(this))
|
||||||
|
@ -55,7 +55,9 @@ public class DatabaseFactory {
|
|||||||
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
|
private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10;
|
||||||
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11;
|
||||||
private static final int INTRODUCED_PUSH_FIX_VERSION = 12;
|
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 String DATABASE_NAME = "messages.db";
|
||||||
private static final Object lock = new Object();
|
private static final Object lock = new Object();
|
||||||
@ -702,6 +704,13 @@ public class DatabaseFactory {
|
|||||||
db.execSQL("DROP TABLE push_backup;");
|
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.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,8 @@ import ws.com.google.android.mms.pdu.EncodedStringValue;
|
|||||||
import ws.com.google.android.mms.pdu.PduHeaders;
|
import ws.com.google.android.mms.pdu.PduHeaders;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class MmsAddressDatabase extends Database {
|
public class MmsAddressDatabase extends Database {
|
||||||
|
|
||||||
@ -106,6 +108,25 @@ public class MmsAddressDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<String> getAddressesForId(long messageId) {
|
||||||
|
List<String> results = new LinkedList<String>();
|
||||||
|
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) {
|
public void deleteAddressesForId(long messageId) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
|
database.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {messageId+""});
|
||||||
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||||
import org.thoughtcrime.securesms.util.LRUCache;
|
import org.thoughtcrime.securesms.util.LRUCache;
|
||||||
|
import org.whispersystems.textsecure.util.InvalidNumberException;
|
||||||
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
import org.whispersystems.textsecure.util.ListenableFutureTask;
|
||||||
import org.thoughtcrime.securesms.util.Trimmer;
|
import org.thoughtcrime.securesms.util.Trimmer;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
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, " +
|
STATUS + " INTEGER, " + TRANSACTION_ID + " TEXT, " + RETRIEVE_STATUS + " INTEGER, " +
|
||||||
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
|
RETRIEVE_TEXT + " TEXT, " + RETRIEVE_TEXT_CS + " INTEGER, " + READ_STATUS + " INTEGER, " +
|
||||||
CONTENT_CLASS + " INTEGER, " + RESPONSE_TEXT + " TEXT, " + DELIVERY_TIME + " 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 = {
|
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_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_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_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[] {
|
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,
|
MESSAGE_SIZE, PRIORITY, REPORT_ALLOWED, STATUS, TRANSACTION_ID, RETRIEVE_STATUS,
|
||||||
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
RETRIEVE_TEXT, RETRIEVE_TEXT_CS, READ_STATUS, CONTENT_CLASS, RESPONSE_TEXT,
|
||||||
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
DELIVERY_TIME, DELIVERY_REPORT, BODY, PART_COUNT, ADDRESS, ADDRESS_DEVICE_ID,
|
||||||
|
RECEIPT_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
|
public static final ExecutorService slideResolver = org.thoughtcrime.securesms.util.Util.newSingleThreadedLifoExecutor();
|
||||||
@ -166,6 +169,43 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
|||||||
return 0;
|
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<String> 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) {
|
public long getThreadIdForMessage(long id) {
|
||||||
String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?";
|
String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?";
|
||||||
String[] sqlArgs = new String[] {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));
|
long outboxType = cursor.getLong(cursor.getColumnIndexOrThrow(MESSAGE_BOX));
|
||||||
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
String messageText = cursor.getString(cursor.getColumnIndexOrThrow(BODY));
|
||||||
|
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(NORMALIZED_DATE_SENT));
|
||||||
PduHeaders headers = getHeadersFromCursor(cursor);
|
PduHeaders headers = getHeadersFromCursor(cursor);
|
||||||
addr.getAddressesForId(messageId, headers);
|
addr.getAddressesForId(messageId, headers);
|
||||||
|
|
||||||
@ -433,7 +474,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
|||||||
Log.w("MmsDatabase", e);
|
Log.w("MmsDatabase", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
requests[i++] = new SendReq(headers, body, messageId, outboxType);
|
requests[i++] = new SendReq(headers, body, messageId, outboxType, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return requests;
|
return requests;
|
||||||
@ -902,6 +943,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
|||||||
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
|
long messageSize = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.MESSAGE_SIZE));
|
||||||
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
|
long expiry = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.EXPIRY));
|
||||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
|
int status = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.STATUS));
|
||||||
|
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
|
||||||
|
|
||||||
byte[]contentLocationBytes = null;
|
byte[]contentLocationBytes = null;
|
||||||
byte[]transactionIdBytes = null;
|
byte[]transactionIdBytes = null;
|
||||||
@ -914,7 +956,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
|||||||
|
|
||||||
|
|
||||||
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
return new NotificationMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||||
addressDeviceId, dateSent, dateReceived, threadId,
|
addressDeviceId, dateSent, dateReceived, receiptCount, threadId,
|
||||||
contentLocationBytes, messageSize, expiry, status,
|
contentLocationBytes, messageSize, expiry, status,
|
||||||
transactionIdBytes, mailbox);
|
transactionIdBytes, mailbox);
|
||||||
}
|
}
|
||||||
@ -927,6 +969,7 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
|||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.THREAD_ID));
|
||||||
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
String address = cursor.getString(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS));
|
||||||
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
|
int addressDeviceId = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.ADDRESS_DEVICE_ID));
|
||||||
|
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.RECEIPT_COUNT));
|
||||||
DisplayRecord.Body body = getBody(cursor);
|
DisplayRecord.Body body = getBody(cursor);
|
||||||
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
|
int partCount = cursor.getInt(cursor.getColumnIndexOrThrow(MmsDatabase.PART_COUNT));
|
||||||
Recipients recipients = getRecipientsFor(address);
|
Recipients recipients = getRecipientsFor(address);
|
||||||
@ -934,8 +977,8 @@ public class MmsDatabase extends Database implements MmsSmsColumns {
|
|||||||
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(masterSecret, id);
|
ListenableFutureTask<SlideDeck> slideDeck = getSlideDeck(masterSecret, id);
|
||||||
|
|
||||||
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
return new MediaMmsMessageRecord(context, id, recipients, recipients.getPrimaryRecipient(),
|
||||||
addressDeviceId, dateSent, dateReceived, threadId, body,
|
addressDeviceId, dateSent, dateReceived, receiptCount,
|
||||||
slideDeck, partCount, box);
|
threadId, body, slideDeck, partCount, box);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipients getRecipientsFor(String address) {
|
private Recipients getRecipientsFor(String address) {
|
||||||
|
@ -10,6 +10,7 @@ public interface MmsSmsColumns {
|
|||||||
public static final String BODY = "body";
|
public static final String BODY = "body";
|
||||||
public static final String ADDRESS = "address";
|
public static final String ADDRESS = "address";
|
||||||
public static final String ADDRESS_DEVICE_ID = "address_device_id";
|
public static final String ADDRESS_DEVICE_ID = "address_device_id";
|
||||||
|
public static final String RECEIPT_COUNT = "delivery_receipt_count";
|
||||||
|
|
||||||
public static class Types {
|
public static class Types {
|
||||||
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
||||||
|
@ -23,8 +23,8 @@ import android.database.sqlite.SQLiteOpenHelper;
|
|||||||
import android.database.sqlite.SQLiteQueryBuilder;
|
import android.database.sqlite.SQLiteQueryBuilder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -49,7 +49,7 @@ public class MmsSmsDatabase extends Database {
|
|||||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||||
MmsDatabase.STATUS, TRANSPORT};
|
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||||
|
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ public class MmsSmsDatabase extends Database {
|
|||||||
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||||
MmsDatabase.STATUS, TRANSPORT};
|
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||||
|
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||||
@ -89,7 +89,7 @@ public class MmsSmsDatabase extends Database {
|
|||||||
MmsDatabase.PART_COUNT,
|
MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY,
|
||||||
MmsDatabase.STATUS, TRANSPORT};
|
MmsDatabase.STATUS, MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||||
|
|
||||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||||
String selection = MmsSmsColumns.READ + " = 0";
|
String selection = MmsSmsColumns.READ + " = 0";
|
||||||
@ -104,6 +104,11 @@ public class MmsSmsDatabase extends Database {
|
|||||||
return count;
|
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) {
|
private Cursor queryTables(String[] projection, String selection, String order, String groupBy, String limit) {
|
||||||
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
String[] mmsProjection = {MmsDatabase.DATE_SENT + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||||
MmsDatabase.DATE_RECEIVED + " * 1000 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
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.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||||
TRANSPORT};
|
MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||||
|
|
||||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
String[] smsProjection = {SmsDatabase.DATE_SENT + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||||
SmsDatabase.DATE_RECEIVED + " * 1 AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
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.MESSAGE_BOX, SmsDatabase.STATUS, MmsDatabase.PART_COUNT,
|
||||||
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
MmsDatabase.CONTENT_LOCATION, MmsDatabase.TRANSACTION_ID,
|
||||||
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
MmsDatabase.MESSAGE_SIZE, MmsDatabase.EXPIRY, MmsDatabase.STATUS,
|
||||||
TRANSPORT};
|
MmsSmsColumns.RECEIPT_COUNT, TRANSPORT};
|
||||||
|
|
||||||
|
|
||||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||||
@ -140,6 +145,7 @@ public class MmsSmsDatabase extends Database {
|
|||||||
mmsColumnsPresent.add(MmsSmsColumns.BODY);
|
mmsColumnsPresent.add(MmsSmsColumns.BODY);
|
||||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS);
|
||||||
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
mmsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||||
|
mmsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
mmsColumnsPresent.add(MmsDatabase.MESSAGE_TYPE);
|
||||||
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
mmsColumnsPresent.add(MmsDatabase.MESSAGE_BOX);
|
||||||
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
mmsColumnsPresent.add(MmsDatabase.DATE_SENT);
|
||||||
@ -158,6 +164,7 @@ public class MmsSmsDatabase extends Database {
|
|||||||
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
smsColumnsPresent.add(MmsSmsColumns.ADDRESS_DEVICE_ID);
|
||||||
smsColumnsPresent.add(MmsSmsColumns.READ);
|
smsColumnsPresent.add(MmsSmsColumns.READ);
|
||||||
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
smsColumnsPresent.add(MmsSmsColumns.THREAD_ID);
|
||||||
|
smsColumnsPresent.add(MmsSmsColumns.RECEIPT_COUNT);
|
||||||
smsColumnsPresent.add(SmsDatabase.TYPE);
|
smsColumnsPresent.add(SmsDatabase.TYPE);
|
||||||
smsColumnsPresent.add(SmsDatabase.SUBJECT);
|
smsColumnsPresent.add(SmsDatabase.SUBJECT);
|
||||||
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
|
smsColumnsPresent.add(SmsDatabase.DATE_SENT);
|
||||||
|
@ -36,13 +36,18 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage;
|
|||||||
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
import org.thoughtcrime.securesms.sms.IncomingKeyExchangeMessage;
|
||||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.thoughtcrime.securesms.util.Trimmer;
|
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 org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.thoughtcrime.securesms.util.Util.canonicalizeNumber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Database for storage of SMS messages.
|
* 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, " +
|
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, " +
|
DATE_RECEIVED + " INTEGER, " + DATE_SENT + " INTEGER, " + PROTOCOL + " INTEGER, " + READ + " INTEGER DEFAULT 0, " +
|
||||||
STATUS + " INTEGER DEFAULT -1," + TYPE + " INTEGER, " + REPLY_PATH_PRESENT + " INTEGER, " +
|
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 = {
|
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_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_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_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[] {
|
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_RECEIVED + " AS " + NORMALIZED_DATE_RECEIVED,
|
||||||
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
DATE_SENT + " AS " + NORMALIZED_DATE_SENT,
|
||||||
PROTOCOL, READ, STATUS, TYPE,
|
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) {
|
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);
|
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) {
|
public void setMessagesRead(long threadId) {
|
||||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||||
ContentValues contentValues = new ContentValues();
|
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 dateSent = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.NORMALIZED_DATE_SENT));
|
||||||
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
|
||||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
|
int status = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.STATUS));
|
||||||
|
int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.RECEIPT_COUNT));
|
||||||
Recipients recipients = getRecipientsFor(address);
|
Recipients recipients = getRecipientsFor(address);
|
||||||
DisplayRecord.Body body = getBody(cursor);
|
DisplayRecord.Body body = getBody(cursor);
|
||||||
|
|
||||||
return new SmsMessageRecord(context, messageId, body, recipients,
|
return new SmsMessageRecord(context, messageId, body, recipients,
|
||||||
recipients.getPrimaryRecipient(),
|
recipients.getPrimaryRecipient(),
|
||||||
addressDeviceId,
|
addressDeviceId,
|
||||||
dateSent, dateReceived, type,
|
dateSent, dateReceived, receiptCount, type,
|
||||||
threadId, status);
|
threadId, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,12 +43,13 @@ public class MediaMmsMessageRecord extends MessageRecord {
|
|||||||
|
|
||||||
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
public MediaMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||||
Recipient individualRecipient, int recipientDeviceId,
|
Recipient individualRecipient, int recipientDeviceId,
|
||||||
long dateSent, long dateReceived, long threadId, Body body,
|
long dateSent, long dateReceived, int deliveredCount,
|
||||||
|
long threadId, Body body,
|
||||||
ListenableFutureTask<SlideDeck> slideDeck,
|
ListenableFutureTask<SlideDeck> slideDeck,
|
||||||
int partCount, long mailbox)
|
int partCount, long mailbox)
|
||||||
{
|
{
|
||||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
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.context = context.getApplicationContext();
|
||||||
this.partCount = partCount;
|
this.partCount = partCount;
|
||||||
|
@ -48,17 +48,19 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
private final int recipientDeviceId;
|
private final int recipientDeviceId;
|
||||||
private final long id;
|
private final long id;
|
||||||
private final int deliveryStatus;
|
private final int deliveryStatus;
|
||||||
|
private final int receiptCount;
|
||||||
|
|
||||||
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
MessageRecord(Context context, long id, Body body, Recipients recipients,
|
||||||
Recipient individualRecipient, int recipientDeviceId,
|
Recipient individualRecipient, int recipientDeviceId,
|
||||||
long dateSent, long dateReceived,
|
long dateSent, long dateReceived, long threadId,
|
||||||
long threadId, int deliveryStatus, long type)
|
int deliveryStatus, int receiptCount, long type)
|
||||||
{
|
{
|
||||||
super(context, body, recipients, dateSent, dateReceived, threadId, type);
|
super(context, body, recipients, dateSent, dateReceived, threadId, type);
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.individualRecipient = individualRecipient;
|
this.individualRecipient = individualRecipient;
|
||||||
this.recipientDeviceId = recipientDeviceId;
|
this.recipientDeviceId = recipientDeviceId;
|
||||||
this.deliveryStatus = deliveryStatus;
|
this.deliveryStatus = deliveryStatus;
|
||||||
|
this.receiptCount = receiptCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract boolean isMms();
|
public abstract boolean isMms();
|
||||||
@ -110,7 +112,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDelivered() {
|
public boolean isDelivered() {
|
||||||
return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED;
|
return getDeliveryStatus() == DELIVERY_STATUS_RECEIVED || receiptCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPush() {
|
public boolean isPush() {
|
||||||
|
@ -42,12 +42,12 @@ public class NotificationMmsMessageRecord extends MessageRecord {
|
|||||||
|
|
||||||
public NotificationMmsMessageRecord(Context context, long id, Recipients recipients,
|
public NotificationMmsMessageRecord(Context context, long id, Recipients recipients,
|
||||||
Recipient individualRecipient, int recipientDeviceId,
|
Recipient individualRecipient, int recipientDeviceId,
|
||||||
long dateSent, long dateReceived, long threadId,
|
long dateSent, long dateReceived, int receiptCount,
|
||||||
byte[] contentLocation, long messageSize, long expiry,
|
long threadId, byte[] contentLocation, long messageSize,
|
||||||
int status, byte[] transactionId, long mailbox)
|
long expiry, int status, byte[] transactionId, long mailbox)
|
||||||
{
|
{
|
||||||
super(context, id, new Body("", true), recipients, individualRecipient, recipientDeviceId,
|
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.contentLocation = contentLocation;
|
||||||
this.messageSize = messageSize;
|
this.messageSize = messageSize;
|
||||||
|
@ -43,11 +43,12 @@ public class SmsMessageRecord extends MessageRecord {
|
|||||||
Recipient individualRecipient,
|
Recipient individualRecipient,
|
||||||
int recipientDeviceId,
|
int recipientDeviceId,
|
||||||
long dateSent, long dateReceived,
|
long dateSent, long dateReceived,
|
||||||
|
int receiptCount,
|
||||||
long type, long threadId,
|
long type, long threadId,
|
||||||
int status)
|
int status)
|
||||||
{
|
{
|
||||||
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
super(context, id, body, recipients, individualRecipient, recipientDeviceId,
|
||||||
dateSent, dateReceived, threadId, getGenericDeliveryStatus(status), type);
|
dateSent, dateReceived, threadId, receiptCount, getGenericDeliveryStatus(status), type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getType() {
|
public long getType() {
|
||||||
|
@ -6,7 +6,10 @@ import android.content.Intent;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.android.gms.gcm.GoogleCloudMessaging;
|
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.service.SendReceiveService;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
import org.whispersystems.libaxolotl.InvalidVersionException;
|
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||||
@ -31,38 +34,47 @@ public class GcmBroadcastReceiver extends BroadcastReceiver {
|
|||||||
if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
|
if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
|
||||||
Log.w(TAG, "GCM message...");
|
Log.w(TAG, "GCM message...");
|
||||||
|
|
||||||
try {
|
if (!TextSecurePreferences.isPushRegistered(context)) {
|
||||||
String data = intent.getStringExtra("message");
|
Log.w(TAG, "Not push registered!");
|
||||||
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
22
src/org/thoughtcrime/securesms/jobs/ContextInjector.java
Normal file
22
src/org/thoughtcrime/securesms/jobs/ContextInjector.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.path.android.jobqueue.BaseJob;
|
||||||
|
import com.path.android.jobqueue.di.DependencyInjector;
|
||||||
|
|
||||||
|
public class ContextInjector implements DependencyInjector {
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public ContextInjector(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void inject(BaseJob job) {
|
||||||
|
if (job instanceof ContextJob) {
|
||||||
|
((ContextJob)job).setContext(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/org/thoughtcrime/securesms/jobs/ContextJob.java
Normal file
23
src/org/thoughtcrime/securesms/jobs/ContextJob.java
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.path.android.jobqueue.Job;
|
||||||
|
import com.path.android.jobqueue.Params;
|
||||||
|
|
||||||
|
public abstract class ContextJob extends Job {
|
||||||
|
|
||||||
|
transient protected Context context;
|
||||||
|
|
||||||
|
protected ContextJob(Params params) {
|
||||||
|
super(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContext(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
56
src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java
Normal file
56
src/org/thoughtcrime/securesms/jobs/DeliveryReceiptJob.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.path.android.jobqueue.Params;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||||
|
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
|
public class DeliveryReceiptJob extends ContextJob {
|
||||||
|
|
||||||
|
private static final String TAG = DeliveryReceiptJob.class.getSimpleName();
|
||||||
|
|
||||||
|
private final String destination;
|
||||||
|
private final long timestamp;
|
||||||
|
private final String relay;
|
||||||
|
|
||||||
|
public DeliveryReceiptJob(String destination, long timestamp, String relay) {
|
||||||
|
super(new Params(Priorities.HIGH).requireNetwork().persist());
|
||||||
|
|
||||||
|
this.destination = destination;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.relay = relay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAdded() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRun() throws Throwable {
|
||||||
|
Log.w("DeliveryReceiptJob", "Sending delivery receipt...");
|
||||||
|
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||||
|
socket.sendReceipt(destination, timestamp, relay);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCancel() {
|
||||||
|
Log.w(TAG, "Failed to send receipt after retry exhausted!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldReRunOnThrowable(Throwable throwable) {
|
||||||
|
Log.w(TAG, throwable);
|
||||||
|
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
|
||||||
|
if (throwable instanceof PushNetworkException) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getRetryLimit() {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
}
|
61
src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
Normal file
61
src/org/thoughtcrime/securesms/jobs/GcmRefreshJob.java
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.google.android.gms.common.ConnectionResult;
|
||||||
|
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||||
|
import com.google.android.gms.gcm.GoogleCloudMessaging;
|
||||||
|
import com.path.android.jobqueue.Params;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.push.PushServiceSocketFactory;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.textsecure.push.PushServiceSocket;
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.MismatchedDevicesException;
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.NonSuccessfulResponseCodeException;
|
||||||
|
import org.whispersystems.textsecure.push.exceptions.PushNetworkException;
|
||||||
|
|
||||||
|
public class GcmRefreshJob extends ContextJob {
|
||||||
|
|
||||||
|
private static final String TAG = GcmRefreshJob.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String REGISTRATION_ID = "312334754206";
|
||||||
|
|
||||||
|
public GcmRefreshJob() {
|
||||||
|
super(new Params(Priorities.NORMAL).requireNetwork());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAdded() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRun() throws Exception {
|
||||||
|
String registrationId = TextSecurePreferences.getGcmRegistrationId(context);
|
||||||
|
|
||||||
|
if (registrationId == null) {
|
||||||
|
Log.w(TAG, "GCM registrationId expired, reregistering...");
|
||||||
|
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(context);
|
||||||
|
|
||||||
|
if (result != ConnectionResult.SUCCESS) {
|
||||||
|
Toast.makeText(context, "Unable to register with GCM!", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
String gcmId = GoogleCloudMessaging.getInstance(context).register(REGISTRATION_ID);
|
||||||
|
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||||
|
|
||||||
|
socket.registerGcmId(gcmId);
|
||||||
|
TextSecurePreferences.setGcmRegistrationId(context, gcmId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCancel() {
|
||||||
|
Log.w(TAG, "GCM reregistration failed after retry attempt exhaustion!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean shouldReRunOnThrowable(Throwable throwable) {
|
||||||
|
if (throwable instanceof NonSuccessfulResponseCodeException) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
27
src/org/thoughtcrime/securesms/jobs/JobLogger.java
Normal file
27
src/org/thoughtcrime/securesms/jobs/JobLogger.java
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.path.android.jobqueue.log.CustomLogger;
|
||||||
|
|
||||||
|
public class JobLogger implements CustomLogger {
|
||||||
|
@Override
|
||||||
|
public boolean isDebugEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void d(String text, Object... args) {
|
||||||
|
Log.w("JobManager", String.format(text, args));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(Throwable t, String text, Object... args) {
|
||||||
|
Log.w("JobManager", String.format(text, args), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void e(String text, Object... args) {
|
||||||
|
Log.w("JobManager", String.format(text, args));
|
||||||
|
}
|
||||||
|
}
|
8
src/org/thoughtcrime/securesms/jobs/Priorities.java
Normal file
8
src/org/thoughtcrime/securesms/jobs/Priorities.java
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
|
public class Priorities {
|
||||||
|
|
||||||
|
public static final int NORMAL = 500;
|
||||||
|
public static final int HIGH = 1000;
|
||||||
|
|
||||||
|
}
|
@ -6,7 +6,6 @@ import android.content.Intent;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.Release;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.EncryptingPartDatabase;
|
import org.thoughtcrime.securesms.database.EncryptingPartDatabase;
|
||||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
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.AttachmentCipherInputStream;
|
||||||
import org.whispersystems.textsecure.crypto.MasterCipher;
|
import org.whispersystems.textsecure.crypto.MasterCipher;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
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.push.PushServiceSocket;
|
||||||
import org.whispersystems.textsecure.util.Base64;
|
import org.whispersystems.textsecure.util.Base64;
|
||||||
|
|
||||||
|
@ -50,6 +50,8 @@ import static org.whispersystems.textsecure.push.PushMessageProtos.PushMessageCo
|
|||||||
|
|
||||||
public class PushReceiver {
|
public class PushReceiver {
|
||||||
|
|
||||||
|
private static final String TAG = PushReceiver.class.getSimpleName();
|
||||||
|
|
||||||
public static final int RESULT_OK = 0;
|
public static final int RESULT_OK = 0;
|
||||||
public static final int RESULT_NO_SESSION = 1;
|
public static final int RESULT_NO_SESSION = 1;
|
||||||
public static final int RESULT_DECRYPT_FAILED = 2;
|
public static final int RESULT_DECRYPT_FAILED = 2;
|
||||||
@ -97,7 +99,9 @@ public class PushReceiver {
|
|||||||
|
|
||||||
if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message);
|
if (message.isSecureMessage()) handleReceivedSecureMessage(masterSecret, message);
|
||||||
else if (message.isPreKeyBundle()) handleReceivedPreKeyBundle(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) {
|
private void handleReceivedSecureMessage(MasterSecret masterSecret, IncomingPushMessage message) {
|
||||||
@ -130,21 +134,21 @@ public class PushReceiver {
|
|||||||
handleReceivedMessage(masterSecret, bundledMessage, true);
|
handleReceivedMessage(masterSecret, bundledMessage, true);
|
||||||
|
|
||||||
} catch (InvalidVersionException e) {
|
} catch (InvalidVersionException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
handleReceivedCorruptedKey(masterSecret, message, true);
|
handleReceivedCorruptedKey(masterSecret, message, true);
|
||||||
} catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException |
|
} catch (InvalidKeyException | InvalidKeyIdException | InvalidMessageException |
|
||||||
RecipientFormattingException | LegacyMessageException e)
|
RecipientFormattingException | LegacyMessageException e)
|
||||||
{
|
{
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
handleReceivedCorruptedKey(masterSecret, message, false);
|
handleReceivedCorruptedKey(masterSecret, message, false);
|
||||||
} catch (DuplicateMessageException e) {
|
} catch (DuplicateMessageException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
handleReceivedDuplicateMessage(message);
|
handleReceivedDuplicateMessage(message);
|
||||||
} catch (NoSessionException e) {
|
} catch (NoSessionException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
handleReceivedMessageForNoSession(masterSecret, message);
|
handleReceivedMessageForNoSession(masterSecret, message);
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
String encoded = Base64.encodeBytes(message.getBody());
|
String encoded = Base64.encodeBytes(message.getBody());
|
||||||
IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
|
IncomingTextMessage textMessage = new IncomingTextMessage(message, encoded, null);
|
||||||
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
|
IncomingPreKeyBundleMessage bundleMessage = new IncomingPreKeyBundleMessage(textMessage, encoded);
|
||||||
@ -163,24 +167,31 @@ public class PushReceiver {
|
|||||||
PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody());
|
PushMessageContent messageContent = PushMessageContent.parseFrom(message.getBody());
|
||||||
|
|
||||||
if (secure && (messageContent.getFlags() & PushMessageContent.Flags.END_SESSION_VALUE) != 0) {
|
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);
|
handleEndSessionMessage(masterSecret, message, messageContent);
|
||||||
} else if (messageContent.hasGroup() && messageContent.getGroup().getType().getNumber() != Type.DELIVER_VALUE) {
|
} 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);
|
groupReceiver.process(masterSecret, message, messageContent, secure);
|
||||||
} else if (messageContent.getAttachmentsCount() > 0) {
|
} else if (messageContent.getAttachmentsCount() > 0) {
|
||||||
Log.w("PushReceiver", "Received push media message...");
|
Log.w(TAG, "Received push media message...");
|
||||||
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
|
handleReceivedMediaMessage(masterSecret, message, messageContent, secure);
|
||||||
} else {
|
} else {
|
||||||
Log.w("PushReceiver", "Received push text message...");
|
Log.w(TAG, "Received push text message...");
|
||||||
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
|
handleReceivedTextMessage(masterSecret, message, messageContent, secure);
|
||||||
}
|
}
|
||||||
} catch (InvalidProtocolBufferException e) {
|
} catch (InvalidProtocolBufferException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
handleReceivedCorruptedMessage(masterSecret, message, secure);
|
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,
|
private void handleEndSessionMessage(MasterSecret masterSecret,
|
||||||
IncomingPushMessage message,
|
IncomingPushMessage message,
|
||||||
PushMessageContent messageContent)
|
PushMessageContent messageContent)
|
||||||
@ -200,7 +211,7 @@ public class PushReceiver {
|
|||||||
|
|
||||||
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
|
KeyExchangeProcessor.broadcastSecurityUpdateEvent(context, messageAndThreadId.second);
|
||||||
} catch (RecipientFormattingException e) {
|
} catch (RecipientFormattingException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,7 +242,7 @@ public class PushReceiver {
|
|||||||
|
|
||||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||||
} catch (MmsException e) {
|
} catch (MmsException e) {
|
||||||
Log.w("PushReceiver", e);
|
Log.w(TAG, e);
|
||||||
// XXX
|
// XXX
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,7 +278,7 @@ public class PushReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleReceivedDuplicateMessage(IncomingPushMessage message) {
|
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,
|
private void handleReceivedCorruptedKey(MasterSecret masterSecret,
|
||||||
|
@ -23,7 +23,7 @@ import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
|||||||
import org.whispersystems.libaxolotl.util.KeyHelper;
|
import org.whispersystems.libaxolotl.util.KeyHelper;
|
||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.PreKeyUtil;
|
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.push.PushServiceSocket;
|
||||||
import org.whispersystems.textsecure.util.Util;
|
import org.whispersystems.textsecure.util.Util;
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ import org.whispersystems.textsecure.crypto.AttachmentCipher;
|
|||||||
import org.whispersystems.textsecure.crypto.MasterSecret;
|
import org.whispersystems.textsecure.crypto.MasterSecret;
|
||||||
import org.whispersystems.textsecure.crypto.TransportDetails;
|
import org.whispersystems.textsecure.crypto.TransportDetails;
|
||||||
import org.whispersystems.textsecure.push.MismatchedDevices;
|
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.OutgoingPushMessage;
|
||||||
import org.whispersystems.textsecure.push.OutgoingPushMessageList;
|
import org.whispersystems.textsecure.push.OutgoingPushMessageList;
|
||||||
import org.whispersystems.textsecure.push.PushAddress;
|
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.PushServiceSocket;
|
||||||
import org.whispersystems.textsecure.push.PushTransportDetails;
|
import org.whispersystems.textsecure.push.PushTransportDetails;
|
||||||
import org.whispersystems.textsecure.push.StaleDevices;
|
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.push.UnregisteredUserException;
|
||||||
import org.whispersystems.textsecure.storage.SessionUtil;
|
import org.whispersystems.textsecure.storage.SessionUtil;
|
||||||
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
|
import org.whispersystems.textsecure.storage.TextSecureSessionStore;
|
||||||
@ -92,7 +92,7 @@ public class PushTransport extends BaseTransport {
|
|||||||
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
PushServiceSocket socket = PushServiceSocketFactory.create(context);
|
||||||
byte[] plaintext = getPlaintextMessage(message);
|
byte[] plaintext = getPlaintextMessage(message);
|
||||||
|
|
||||||
deliver(socket, recipient, threadId, plaintext);
|
deliver(socket, recipient, message.getDateSent(), threadId, plaintext);
|
||||||
|
|
||||||
if (message.isEndSession()) {
|
if (message.isEndSession()) {
|
||||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||||
@ -129,7 +129,7 @@ public class PushTransport extends BaseTransport {
|
|||||||
|
|
||||||
for (Recipient recipient : recipients.getRecipientsList()) {
|
for (Recipient recipient : recipients.getRecipientsList()) {
|
||||||
try {
|
try {
|
||||||
deliver(socket, recipient, threadId, plaintext);
|
deliver(socket, recipient, message.getSentTimestamp(), threadId, plaintext);
|
||||||
} catch (UntrustedIdentityException e) {
|
} catch (UntrustedIdentityException e) {
|
||||||
Log.w("PushTransport", e);
|
Log.w("PushTransport", e);
|
||||||
untrustedIdentities.add(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
|
throws IOException, InvalidNumberException, UntrustedIdentityException
|
||||||
{
|
{
|
||||||
for (int i=0;i<3;i++) {
|
for (int i=0;i<3;i++) {
|
||||||
try {
|
try {
|
||||||
OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId,
|
OutgoingPushMessageList messages = getEncryptedMessages(socket, threadId, recipient,
|
||||||
recipient, plaintext);
|
timestamp, plaintext);
|
||||||
socket.sendMessage(messages);
|
socket.sendMessage(messages);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -300,7 +301,8 @@ public class PushTransport extends BaseTransport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
|
private OutgoingPushMessageList getEncryptedMessages(PushServiceSocket socket, long threadId,
|
||||||
Recipient recipient, byte[] plaintext)
|
Recipient recipient, long timestamp,
|
||||||
|
byte[] plaintext)
|
||||||
throws IOException, InvalidNumberException, UntrustedIdentityException
|
throws IOException, InvalidNumberException, UntrustedIdentityException
|
||||||
{
|
{
|
||||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||||
@ -319,7 +321,7 @@ public class PushTransport extends BaseTransport {
|
|||||||
messages.add(new OutgoingPushMessage(device, body));
|
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,
|
private PushBody getEncryptedMessage(PushServiceSocket socket, long threadId,
|
||||||
|
@ -25,6 +25,7 @@ public class SendReq extends MultimediaMessagePdu {
|
|||||||
private static final String TAG = "SendReq";
|
private static final String TAG = "SendReq";
|
||||||
private long databaseMessageId;
|
private long databaseMessageId;
|
||||||
private long messageBox;
|
private long messageBox;
|
||||||
|
private long timestamp;
|
||||||
|
|
||||||
public SendReq() {
|
public SendReq() {
|
||||||
super();
|
super();
|
||||||
@ -90,11 +91,12 @@ public class SendReq extends MultimediaMessagePdu {
|
|||||||
super(headers, body);
|
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);
|
super(headers, body);
|
||||||
this.databaseMessageId = messageId;
|
this.databaseMessageId = messageId;
|
||||||
this.messageBox = messageBox;
|
this.messageBox = messageBox;
|
||||||
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDatabaseMessageBox() {
|
public long getDatabaseMessageBox() {
|
||||||
@ -105,6 +107,10 @@ public class SendReq extends MultimediaMessagePdu {
|
|||||||
return databaseMessageId;
|
return databaseMessageId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getSentTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Bcc value.
|
* Get Bcc value.
|
||||||
*
|
*
|
||||||
|
Loading…
x
Reference in New Issue
Block a user