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